From e206e4326810e9f586e471f89e2b41e4e1c539ea Mon Sep 17 00:00:00 2001 From: dgflash Date: Sat, 21 Feb 2026 22:17:01 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=B2=A1=E7=94=A8?= =?UTF-8?q?=E5=88=B0=E7=9A=84=E8=B5=84=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/asset-directory.js | 8 ++++---- dist/assets-menu.js | 3 ++- dist/common/version.js | 31 ++++++++++------------------ dist/main.js | 6 +++--- dist/tinypng.js | 3 ++- static/style/default/index.css | 37 ---------------------------------- 6 files changed, 22 insertions(+), 66 deletions(-) delete mode 100644 static/style/default/index.css diff --git a/dist/asset-directory.js b/dist/asset-directory.js index 24d6088..05bba40 100644 --- a/dist/asset-directory.js +++ b/dist/asset-directory.js @@ -1,9 +1,6 @@ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); -exports.template = exports.$ = void 0; -exports.update = update; -exports.ready = ready; -exports.close = close; +exports.close = exports.ready = exports.update = exports.template = exports.$ = void 0; const fs_1 = require("fs"); const path_1 = require("path"); exports.$ = { @@ -41,5 +38,8 @@ function update(assetList, metaList) { this.$.section.hidden = false; } } +exports.update = update; function ready() { } +exports.ready = ready; function close() { } +exports.close = close; diff --git a/dist/assets-menu.js b/dist/assets-menu.js index cc7d2dc..a8c969c 100644 --- a/dist/assets-menu.js +++ b/dist/assets-menu.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.onAssetMenu = onAssetMenu; +exports.onAssetMenu = void 0; const tinypng_1 = require("./tinypng"); /** 资源栏右键菜单 */ function onAssetMenu(assetInfo) { @@ -23,4 +23,5 @@ function onAssetMenu(assetInfo) { }, ]; } +exports.onAssetMenu = onAssetMenu; ; diff --git a/dist/common/version.js b/dist/common/version.js index 1b1b2a7..53ca430 100644 --- a/dist/common/version.js +++ b/dist/common/version.js @@ -15,27 +15,15 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.checkUpdate = checkUpdate; -exports.statistics = statistics; -exports.reload = reload; +exports.reload = exports.statistics = exports.checkUpdate = void 0; const package_util_1 = require("./package-util"); const https = __importStar(require("https")); /** @@ -80,6 +68,7 @@ function checkUpdate() { console.error("【Oops Framework】请检查你的网络是否正常,框架版本验证失败"); }); } +exports.checkUpdate = checkUpdate; async function statistics() { // 获取本地 IP 地址 const os = require('os'); @@ -123,12 +112,14 @@ async function statistics() { req.write(postData); req.end(); } +exports.statistics = statistics; async function reload() { const path = await Editor.Package.getPath(package_util_1.PackageUtil.name); await Editor.Package.unregister(path); await Editor.Package.register(path); await Editor.Package.enable(path); } +exports.reload = reload; /** * 获取本地版本号 * @returns {string} diff --git a/dist/main.js b/dist/main.js index 2e9c340..4e4a575 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,8 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.methods = exports.config = void 0; -exports.load = load; -exports.unload = unload; +exports.methods = exports.config = exports.unload = exports.load = void 0; const electron_1 = require("electron"); const version_1 = require("./common/version"); /** @@ -13,11 +11,13 @@ function load() { (0, version_1.checkUpdate)(); (0, version_1.statistics)(); } +exports.load = load; /** * @en Hooks triggered after extension uninstallation is complete * @zh 扩展卸载完成后触发的钩子 */ function unload() { } +exports.unload = unload; /** * @en * @zh 为扩展的主进程的注册方法 diff --git a/dist/tinypng.js b/dist/tinypng.js index f86e916..79391e2 100644 --- a/dist/tinypng.js +++ b/dist/tinypng.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.compress = compress; +exports.compress = void 0; const fs_1 = __importDefault(require("fs")); const https_1 = __importDefault(require("https")); const path_1 = __importDefault(require("path")); @@ -59,6 +59,7 @@ function compress(filePath) { }); } } +exports.compress = compress; function getRandomIP() { return Array.from(Array(4)).map(() => Math.floor(255 * Math.random())).join('.'); } diff --git a/static/style/default/index.css b/static/style/default/index.css deleted file mode 100644 index a856db3..0000000 --- a/static/style/default/index.css +++ /dev/null @@ -1,37 +0,0 @@ -ui-input { - width: 100%; - min-width: 100px; - min-height: 30px; - line-height: 30px; -} - -ui-button { - height: 30px; -} - -ui-label { - margin-top: 5px; - margin-bottom: 5px; -} - -.f { - flex: 1; - overflow: auto; - padding: 10px; -} - -.c { - display: inline-flex; - width: 100%; - flex-wrap: nowrap; -} - -.c ui-button { - width: 40%; -} - -.b { - text-align: center; - padding: 10px 0; - margin-top: 5px; -} \ No newline at end of file From a2264505647a19ebf72c3441302fb970f3da3397 Mon Sep 17 00:00:00 2001 From: dgflash Date: Sun, 22 Feb 2026 18:37:27 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E5=B1=82=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E8=87=AA=E5=8A=A8=E7=BB=91=E5=AE=9A=E5=88=B0=E5=AE=9E?= =?UTF-8?q?=E4=BD=93=E5=B1=9E=E6=80=A7=E4=B8=8A=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/libs/ecs/ECSEntity.ts | 11 +++++---- assets/module/common/CCEntity.ts | 7 ++++++ assets/module/common/CCView.ts | 40 ++++++++++++++++---------------- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/assets/libs/ecs/ECSEntity.ts b/assets/libs/ecs/ECSEntity.ts index 99f1798..3e6171a 100644 --- a/assets/libs/ecs/ECSEntity.ts +++ b/assets/libs/ecs/ECSEntity.ts @@ -269,7 +269,7 @@ export class ECSEntity { remove(ctor: CompType, isRecycle = true): void { const componentTypeId = typeof ctor === 'number' ? ctor : ctor.tid; const compName = typeof ctor === 'number' ? '' : ctor.compName; - + if (!this.mask.has(componentTypeId)) { return; } @@ -280,7 +280,7 @@ export class ECSEntity { } comp.ent = null!; - + if (isRecycle) { comp.reset(); @@ -296,7 +296,8 @@ export class ECSEntity { // 限制缓存组件数量,防止内存泄漏 if (this.compTid2Obj.size < ECSEntity.MAX_CACHE_COMP) { this.compTid2Obj.set(componentTypeId, comp); // 用于缓存显示对象组件 - } else { + } + else { // 超过限制,强制回收 console.warn(`实体 ${this.name} 缓存组件数量超过限制,强制回收组件 ${compName}`); comp.reset(); @@ -338,10 +339,10 @@ export class ECSEntity { // 移除实体上所有组件 this.compTid2Ctor.forEach(this._remove, this); destroyEntity(this); - + // 清理缓存的组件对象,防止内存泄漏 this.compTid2Obj.clear(); - + // 回收 mask 到对象池 this.mask.destroy(); } diff --git a/assets/module/common/CCEntity.ts b/assets/module/common/CCEntity.ts index f3f9bd0..869da9a 100644 --- a/assets/module/common/CCEntity.ts +++ b/assets/module/common/CCEntity.ts @@ -224,6 +224,10 @@ export abstract class CCEntity extends ecs.Entity { //@ts-ignore business.init(); this.businesss.set(cls, business); + + // 将业务逻辑组件直接附加到实体对象身上,方便直接获取 + Reflect.set(this, cls.name, business); + return business as T; } @@ -247,6 +251,9 @@ export abstract class CCEntity extends ecs.Entity { if (business) { business.destroy(); this.businesss.delete(cls); + + // 清理实体上的业务逻辑组件引用 + Reflect.set(this, cls.name, null); } } } diff --git a/assets/module/common/CCView.ts b/assets/module/common/CCView.ts index 19d1703..f8f67ad 100644 --- a/assets/module/common/CCView.ts +++ b/assets/module/common/CCView.ts @@ -43,7 +43,7 @@ export class RoleViewComp extends CCView { @ecs.register('LoadingView', false) export class LoadingViewComp extends CCView { protected mvvm = true; // 启用 MVVM 功能 - + data: LoadingData = { finished: 0, total: 0, @@ -70,15 +70,15 @@ export abstract class CCView extends GameComponent implement //#region MVVM 功能相关(仅在 mvvm = true 时使用) /** 是否启用 MVVM 功能(子类可覆盖为 true) */ - protected mvvm: boolean = false; - - /** + protected mvvm = false; + + /** * MVVM 绑定的标签,延迟初始化以节省内存 * 仅在启用 MVVM 时创建 */ protected tag?: string; - - /** + + /** * 需要绑定的私有数据 * 注意:子类应该显式初始化此属性 */ @@ -109,11 +109,11 @@ export abstract class CCView extends GameComponent implement // 优化:只在必要时替换点号,使用更快的 replaceAll(如果支持) this.tag = `_temp<${uuid.replace('.', '')}>`; VM.add(this.data!, this.tag); - + // 搜寻所有节点:找到 watch path const comps = this.getVMComponents(); const len = comps.length; - + // 优化:避免属性查找,缓存 tag const tag = this.tag; for (let i = 0; i < len; i++) { @@ -143,7 +143,7 @@ export abstract class CCView extends GameComponent implement // @ts-ignore - 优化:使用 any 类型避免多次类型转换 const vmComp: any = comp; const path: string = vmComp.watchPath; - + // 优化:使用严格相等避免类型转换 if (vmComp.templateMode === true) { const pathArr: string[] = vmComp.watchPathArr; @@ -171,20 +171,20 @@ export abstract class CCView extends GameComponent implement */ private getVMComponents(): Component[] { const comps = this.node.getComponentsInChildren(VMBase); - + // 优化:提前返回,避免不必要的计算 if (comps.length === 0) { return comps; } - + // 优化:只在有嵌套 CCView 时才获取 parents const parents = this.node.getComponentsInChildren(CCView); - + // 优化:使用数组长度判断,避免创建新数组 let hasNested = false; const len = parents.length; const myUuid = this.uuid; - + for (let i = 0; i < len; i++) { const p = parents[i]; if (p.uuid !== myUuid && p.mvvm) { @@ -219,7 +219,7 @@ export abstract class CCView extends GameComponent implement result.push(comps[i]); } } - + return result; } @@ -229,20 +229,20 @@ export abstract class CCView extends GameComponent implement console.error(`组件 ${this.name} 移除失败,实体不存在`); return; } - + if (this.tid < 0) { console.error(`组件 ${this.name} 移除失败,组件未注册 (tid=${this.tid})`); return; } - + const cct = ECSModel.compCtors[this.tid]; if (!cct) { console.error(`组件 ${this.name} 移除失败,组件构造函数不存在 (tid=${this.tid})`); return; } - + this.ent.removeUi(cct); - this.ent = null!; // 清空引用,避免内存泄漏 + this.ent = null!; // 清空引用,避免内存泄漏 } /** @@ -260,7 +260,7 @@ export abstract class CCView extends GameComponent implement // @ts-ignore - 优化:显式清空引用,帮助 GC this.tag = undefined; } - + // @ts-ignore - 优化:显式清空引用,帮助 GC this.data = undefined; } @@ -269,4 +269,4 @@ export abstract class CCView extends GameComponent implement } abstract reset(): void; -} \ No newline at end of file +} From fca918c47c54cbcdbeee4f3503f71a7e851d079a Mon Sep 17 00:00:00 2001 From: dgflash Date: Sun, 22 Feb 2026 21:24:44 +0800 Subject: [PATCH 3/7] =?UTF-8?q?CCEntity.addUi=E4=B8=8ECCEntity.removeUi?= =?UTF-8?q?=E6=94=AF=E6=8C=81CCView=E4=B8=8EGameComponent=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E7=95=8C=E9=9D=A2=E6=89=93=E5=BC=80=E4=B8=8E?= =?UTF-8?q?=E7=A7=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/module/common/CCEntity.ts | 43 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/assets/module/common/CCEntity.ts b/assets/module/common/CCEntity.ts index 869da9a..3f6cab0 100644 --- a/assets/module/common/CCEntity.ts +++ b/assets/module/common/CCEntity.ts @@ -12,11 +12,21 @@ import type { CCBusiness } from './CCBusiness'; import type { CCView } from './CCView'; import { GameComponent } from './GameComponent'; +/** ECS 组件构造函数类型(用于继承自 ecs.Comp 的组件) */ type ECSCtor = | __private.__types_globals__Constructor | __private.__types_globals__AbstractedConstructor; -type ECSView = CCView; +/** UI 组件构造函数类型(用于继承自 GameComponent 并使用 gui.register 注册的组件) */ +type UICtor = + | __private.__types_globals__Constructor + | __private.__types_globals__AbstractedConstructor; +/** ECS 游戏视图组件类型(继承自 CCView,用于完整的 ECS 组件) */ +export type ECSView = CCView; +/** GUI 视图组件类型(继承自 GameComponent,使用 @gui.register 装饰器注册的组件) */ +export type GUIView = GameComponent; +/** ECS 实体构造函数类型 */ type EntityCtor = new (...args: any[]) => T; +/** ECS 业务逻辑组件构造函数类型 */ type BusinessCtor = CCBusiness> = new (...args: any[]) => T; /** ECS 游戏模块实体 */ @@ -123,11 +133,11 @@ export abstract class CCEntity extends ecs.Entity { /** * 添加视图层组件 - * @param ctor 界面逻辑组件 + * @param ctor 界面逻辑组件(支持 CCView 或使用 gui.register 注册的 GameComponent 子类) * @param params 界面参数 * @returns 界面节点 */ - async addUi(ctor: ECSCtor, params?: UIParam): Promise { + async addUi(ctor: UICtor, params?: UIParam): Promise { const key = gui.internal.getKey(ctor); if (!key) { throw new Error(`${ctor.name} 界面组件未使用 gui.register 注册`); @@ -146,21 +156,18 @@ export abstract class CCEntity extends ecs.Entity { } const node = await oops.gui.open(key, params); - const comp = node.getComponent(ctor) as ecs.Comp; - if (!comp) { - throw new Error(`界面节点上未找到组件 ${ctor.name}`); - } + const comp = node.getComponent(ctor) as unknown as ecs.Comp; + if (comp) this.add(comp); - this.add(comp); oops.gui.show(key); return node; } /** * 移除视图层组件 - * @param ctor 界面逻辑组件 + * @param ctor 界面逻辑组件(支持 CCView 或使用 gui.register 注册的 GameComponent 子类) */ - removeUi(ctor: CompType) { + removeUi(ctor: UICtor) { const key = gui.internal.getKey(ctor); if (key) { @@ -170,12 +177,15 @@ export abstract class CCEntity extends ecs.Entity { return; } - const comp = node.getComponent(LayerUIElement); - if (comp) { + const layer = node.getComponent(LayerUIElement); + if (layer) { // 处理界面关闭动画播放完成后,移除ECS组件,避免使用到组件实体数据还在动画播放时在使用导致的空对象问题 - comp.onClose = () => { + layer.onClose = () => { try { - this.remove(ctor); + const view = node.getComponent(ctor) as unknown as ecs.Comp; + if (view) { + this.remove(ctor as unknown as CompType); + } } catch (error) { console.error(`移除界面组件失败: ${key}`, error); @@ -185,11 +195,12 @@ export abstract class CCEntity extends ecs.Entity { } else { // 没有 LayerUIElement,直接移除 - this.remove(ctor); + this.remove(ctor as unknown as CompType); } } else { - this.remove(ctor); + // 组件未使用 gui.register 注册,尝试直接移除 + this.remove(ctor as unknown as CompType); } } //#endregion From 2da486bd77bdd954d15df76723327a1fc27fa641 Mon Sep 17 00:00:00 2001 From: dgflash Date: Sun, 22 Feb 2026 22:47:27 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E5=BA=9F=E5=BC=83=E8=80=81=E7=89=88?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E5=8E=8B=E7=BC=A9=E5=B7=A5=E5=85=B7=EF=BC=8C?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=B7=A5=E5=85=B7=E9=9B=86=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E4=BB=A3=E6=9B=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/module/common/CCEntity.ts | 4 +- dist/assets-menu.js | 20 +--- dist/tinypng.js | 160 ------------------------------- i18n/en.js | 4 +- i18n/zh.js | 4 +- package.json | 7 +- src/assets-menu.ts | 22 +---- src/tinypng.ts | 159 ------------------------------ 8 files changed, 7 insertions(+), 373 deletions(-) delete mode 100644 dist/tinypng.js delete mode 100644 src/tinypng.ts diff --git a/assets/module/common/CCEntity.ts b/assets/module/common/CCEntity.ts index 3f6cab0..212ad8c 100644 --- a/assets/module/common/CCEntity.ts +++ b/assets/module/common/CCEntity.ts @@ -183,9 +183,7 @@ export abstract class CCEntity extends ecs.Entity { layer.onClose = () => { try { const view = node.getComponent(ctor) as unknown as ecs.Comp; - if (view) { - this.remove(ctor as unknown as CompType); - } + if (view) this.remove(ctor as unknown as CompType); } catch (error) { console.error(`移除界面组件失败: ${key}`, error); diff --git a/dist/assets-menu.js b/dist/assets-menu.js index a8c969c..709fdb2 100644 --- a/dist/assets-menu.js +++ b/dist/assets-menu.js @@ -1,27 +1,9 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.onAssetMenu = void 0; -const tinypng_1 = require("./tinypng"); /** 资源栏右键菜单 */ function onAssetMenu(assetInfo) { - return [ - { - label: 'i18n:oops-framework.name', - submenu: [ - { - label: `i18n:oops-framework.tools_asset_menu`, - submenu: [ - { - label: `i18n:oops-framework.tools_compress`, - click() { - (0, tinypng_1.compress)(assetInfo.file); - }, - } - ] - } - ], - }, - ]; + return []; } exports.onAssetMenu = onAssetMenu; ; diff --git a/dist/tinypng.js b/dist/tinypng.js deleted file mode 100644 index 79391e2..0000000 --- a/dist/tinypng.js +++ /dev/null @@ -1,160 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.compress = void 0; -const fs_1 = __importDefault(require("fs")); -const https_1 = __importDefault(require("https")); -const path_1 = __importDefault(require("path")); -const url_1 = __importDefault(require("url")); -const exts = ['.png', '.jpg', '.jpeg']; -const max = 5200000; -const options = { - method: 'POST', - hostname: 'tinypng.com', - path: '/backend/opt/shrink', - headers: { - rejectUnauthorized: 'false', - 'Postman-Token': Date.now(), - 'Cache-Control': 'no-cache', - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36' - } -}; -function compress(filePath) { - if (!fs_1.default.existsSync(filePath)) { - console.log(`路径不存在:${filePath}`); - return; - } - const fileName = path_1.default.basename(filePath); - if (!fs_1.default.statSync(filePath).isDirectory()) { - if (exts.includes(path_1.default.extname(filePath))) { - console.log(`[${fileName}] 压缩中...`); - fileTinyUpload(filePath) - .then(data => { - console.log(`[1/1] [${fileName}] 压缩成功,原始: ${toSize(data.input.size)},压缩: ${toSize(data.output.size)},压缩比: ${toPercent(data.output.ratio)}`); - }) - .catch(err => { - console.log(`[1/1] [${fileName}] 压缩失败!报错:${err}`); - }); - } - else { - console.log(`[${fileName}] 压缩失败!报错:只支持 png、jpg 与 jpeg 格式`); - } - } - else { - let totalCount = 0; - let processedCount = 0; - fileEach(filePath, (filePathInDir) => { - totalCount++; - const relativePath = path_1.default.relative(filePath, filePathInDir); - fileTinyUpload(filePathInDir) - .then(data => { - console.log(`[${++processedCount}/${totalCount}] [${relativePath}] 压缩成功,原始: ${toSize(data.input.size)},压缩: ${toSize(data.output.size)},压缩比: ${toPercent(data.output.ratio)}`); - }) - .catch(err => { - console.log(`[${++processedCount}/${totalCount}] [${relativePath}] 压缩失败!报错:${err}`); - }); - }); - } -} -exports.compress = compress; -function getRandomIP() { - return Array.from(Array(4)).map(() => Math.floor(255 * Math.random())).join('.'); -} -function fileEach(dir, callback) { - fs_1.default.readdir(dir, (err, files) => { - if (err) { - console.error(err); - return; - } - files.forEach((file) => { - const filePath = path_1.default.join(dir, file); - fs_1.default.stat(filePath, (statErr, stats) => { - if (statErr) { - console.error(statErr); - return; - } - if (stats.isDirectory()) { - fileEach(filePath, callback); - } - else { - if (stats.size <= max && stats.isFile() && exts.includes(path_1.default.extname(file))) { - callback(filePath); - } - } - }); - }); - }); -} -function fileUpload(filePath) { - return new Promise((resolve, reject) => { - options.headers['X-Forwarded-For'] = getRandomIP(); - const req = https_1.default.request(options, (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - try { - const result = JSON.parse(data); - if (result.error) { - reject(result.message); - } - else { - resolve(result); - } - } - catch (parseErr) { - reject(parseErr); - } - }); - }); - req.write(fs_1.default.readFileSync(filePath), 'binary'); - req.on('error', err => { - reject(err); - }); - req.end(); - }); -} -function fileUpdate(filePath, data) { - return new Promise((resolve, reject) => { - const urlObj = new url_1.default.URL(data.output.url); - const req = https_1.default.request(urlObj, (res) => { - let body = ''; - res.setEncoding('binary'); - res.on('data', (chunk) => { - body += chunk; - }); - res.on('end', () => { - fs_1.default.writeFile(filePath, body, 'binary', (err) => { - if (err) { - reject(err); - } - else { - resolve(data); - } - }); - }); - }); - req.on('error', (err) => { - reject(err); - }); - req.end(); - }); -} -function fileTinyUpload(filePath) { - return fileUpload(filePath).then(data => fileUpdate(filePath, data)); -} -function toSize(size) { - if (size < 1024) - return size + 'B'; - else if (size < 1048576) - return (size / 1024).toFixed(2) + 'KB'; - else - return (size / 1024 / 1024).toFixed(2) + 'MB'; -} -function toPercent(ratio) { - return (100 * ratio).toFixed(2) + '%'; -} diff --git a/i18n/en.js b/i18n/en.js index afe506e..f6b2d6d 100644 --- a/i18n/en.js +++ b/i18n/en.js @@ -19,8 +19,6 @@ module.exports = { createView: "Create ECS view layer script", createViewMvvm: "Create ECS view layer script - MVVM", tools: "Framework Tools", - tools_asset_menu: "Tools", - tools_compress: "Image Compression", tools_animator_editor: "Animation State Machine Editor", panel_create_file: "Create Framework Template", -}; \ No newline at end of file +}; diff --git a/i18n/zh.js b/i18n/zh.js index 79f9f54..a98631a 100644 --- a/i18n/zh.js +++ b/i18n/zh.js @@ -19,8 +19,6 @@ module.exports = { createView: "创建 ECS 视图层脚本", createViewMvvm: "创建 ECS 视图层脚本 - MVVM", tools: "框架工具", - tools_asset_menu: "工具", - tools_compress: "图片压缩", tools_animator_editor: "动画状态机编辑器", panel_create_file: "创建框架模板" -}; \ No newline at end of file +}; diff --git a/package.json b/package.json index 68c20c5..1b7e98b 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,7 @@ "readonly": false } }, - "assets": { - "menu": { - "methods": "./dist/assets-menu.js", - "assetMenu": "onAssetMenu" - } - }, + "menu": [ { "path": "i18n:oops-framework.name/i18n:oops-framework.tools", diff --git a/src/assets-menu.ts b/src/assets-menu.ts index efc99b8..1e953d6 100644 --- a/src/assets-menu.ts +++ b/src/assets-menu.ts @@ -1,24 +1,6 @@ import { AssetInfo } from "../@types/packages/asset-db/@types/public"; -import { compress } from "./tinypng"; /** 资源栏右键菜单 */ export function onAssetMenu(assetInfo: AssetInfo) { - return [ - { - label: 'i18n:oops-framework.name', - submenu: [ - { - label: `i18n:oops-framework.tools_asset_menu`, - submenu: [ - { - label: `i18n:oops-framework.tools_compress`, - click() { - compress(assetInfo.file); - }, - } - ] - } - ], - }, - ]; -}; \ No newline at end of file + return []; +}; diff --git a/src/tinypng.ts b/src/tinypng.ts deleted file mode 100644 index d48be3c..0000000 --- a/src/tinypng.ts +++ /dev/null @@ -1,159 +0,0 @@ -import fs from 'fs'; -import https from 'https'; -import path from 'path'; -import url from 'url'; - -const exts = ['.png', '.jpg', '.jpeg']; -const max = 5200000; -const options: any = { - method: 'POST', - hostname: 'tinypng.com', - path: '/backend/opt/shrink', - headers: { - rejectUnauthorized: 'false', - 'Postman-Token': Date.now(), - 'Cache-Control': 'no-cache', - 'Content-Type': 'application/x-www-form-urlencoded', - 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36' - } -}; - -export function compress(filePath: string): void { - if (!fs.existsSync(filePath)) { - console.log(`路径不存在:${filePath}`); - return; - } - const fileName = path.basename(filePath); - if (!fs.statSync(filePath).isDirectory()) { - if (exts.includes(path.extname(filePath))) { - console.log(`[${fileName}] 压缩中...`); - fileTinyUpload(filePath) - .then(data => { - console.log(`[1/1] [${fileName}] 压缩成功,原始: ${toSize(data.input.size)},压缩: ${toSize(data.output.size)},压缩比: ${toPercent(data.output.ratio)}`); - }) - .catch(err => { - console.log(`[1/1] [${fileName}] 压缩失败!报错:${err}`); - }); - } - else { - console.log(`[${fileName}] 压缩失败!报错:只支持 png、jpg 与 jpeg 格式`); - } - } - else { - let totalCount = 0; - let processedCount = 0; - fileEach(filePath, (filePathInDir) => { - totalCount++; - const relativePath = path.relative(filePath, filePathInDir); - fileTinyUpload(filePathInDir) - .then(data => { - console.log(`[${++processedCount}/${totalCount}] [${relativePath}] 压缩成功,原始: ${toSize(data.input.size)},压缩: ${toSize(data.output.size)},压缩比: ${toPercent(data.output.ratio)}`); - }) - .catch(err => { - console.log(`[${++processedCount}/${totalCount}] [${relativePath}] 压缩失败!报错:${err}`); - }); - }); - } -} - -function getRandomIP(): string { - return Array.from(Array(4)).map(() => Math.floor(255 * Math.random())).join('.'); -} - -function fileEach(dir: string, callback: (filePath: string) => void): void { - fs.readdir(dir, (err: any, files: any[]) => { - if (err) { - console.error(err); - return; - } - files.forEach((file: any) => { - const filePath = path.join(dir, file); - fs.stat(filePath, (statErr: any, stats: { isDirectory: () => any; size: number; isFile: () => any; }) => { - if (statErr) { - console.error(statErr); - return; - } - if (stats.isDirectory()) { - fileEach(filePath, callback); - } - else { - if (stats.size <= max && stats.isFile() && exts.includes(path.extname(file))) { - callback(filePath); - } - } - }); - }); - }); -} - -function fileUpload(filePath: string): Promise { - return new Promise((resolve, reject) => { - options.headers['X-Forwarded-For'] = getRandomIP(); - const req = https.request(options, (res: any) => { - let data = ''; - res.on('data', (chunk: string) => { - data += chunk; - }); - res.on('end', () => { - try { - const result = JSON.parse(data); - if (result.error) { - reject(result.message); - } else { - resolve(result); - } - } catch (parseErr) { - reject(parseErr); - } - }); - }); - req.write(fs.readFileSync(filePath), 'binary'); - req.on('error', err => { - reject(err); - }); - req.end(); - }); -} - -function fileUpdate(filePath: string, data: any): Promise { - return new Promise((resolve, reject) => { - const urlObj = new url.URL(data.output.url); - const req = https.request(urlObj, (res: any) => { - let body = ''; - res.setEncoding('binary'); - res.on('data', (chunk: string) => { - body += chunk; - }); - res.on('end', () => { - fs.writeFile(filePath, body, 'binary', (err: any) => { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); - }); - req.on('error', (err: any) => { - reject(err); - }); - req.end(); - }); -} - -function fileTinyUpload(filePath: string): Promise { - return fileUpload(filePath).then(data => fileUpdate(filePath, data)); -} - -function toSize(size: number): string { - if (size < 1024) - return size + 'B'; - else if (size < 1048576) - return (size / 1024).toFixed(2) + 'KB'; - else - return (size / 1024 / 1024).toFixed(2) + 'MB'; -} - -function toPercent(ratio: number): string { - return (100 * ratio).toFixed(2) + '%'; -} \ No newline at end of file From aec1a6567fb3d40c459071a04d1397d099a878d0 Mon Sep 17 00:00:00 2001 From: dgflash Date: Mon, 23 Feb 2026 11:53:30 +0800 Subject: [PATCH 5/7] =?UTF-8?q?1.=20=E4=B8=BA=20Gui.ts=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=AE=8C=E6=95=B4=E7=B1=BB=E5=9E=8B=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=202.=E4=BF=AE=E5=A4=8D=20CCBusiness=20=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E9=87=8A=E6=94=BE=E6=96=B9=E6=B3=95=203.=E6=8F=90=E5=8F=96=20C?= =?UTF-8?q?CEntity=20=E7=B1=BB=E5=9E=8B=E8=87=B3=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=EF=BC=8C=E4=BC=98=E5=8C=96=20addPrefab=20?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/core/gui/Gui.ts | 28 +++++---- assets/module/common/CCBusiness.ts | 2 +- assets/module/common/CCEntity.ts | 58 +++++++------------ assets/module/decorator.meta | 9 +++ .../module/decorator/GamePrefabDecorator.ts | 32 ++++++++++ .../decorator/GamePrefabDecorator.ts.meta | 9 +++ assets/module/decorator/index.ts | 8 +++ assets/module/decorator/index.ts.meta | 9 +++ assets/module/types.meta | 9 +++ assets/module/types/Types.ts | 37 ++++++++++++ assets/module/types/Types.ts.meta | 9 +++ 11 files changed, 158 insertions(+), 52 deletions(-) create mode 100644 assets/module/decorator.meta create mode 100644 assets/module/decorator/GamePrefabDecorator.ts create mode 100644 assets/module/decorator/GamePrefabDecorator.ts.meta create mode 100644 assets/module/decorator/index.ts create mode 100644 assets/module/decorator/index.ts.meta create mode 100644 assets/module/types.meta create mode 100644 assets/module/types/Types.ts create mode 100644 assets/module/types/Types.ts.meta diff --git a/assets/core/gui/Gui.ts b/assets/core/gui/Gui.ts index b126489..48878a9 100644 --- a/assets/core/gui/Gui.ts +++ b/assets/core/gui/Gui.ts @@ -1,3 +1,4 @@ +import type { GameComponentCtor, UICtor } from '../../module/types/Types'; import type { UIConfigMap } from './layer/LayerEnum'; import type { UIConfig } from './layer/UIConfig'; @@ -5,10 +6,9 @@ const configs: UIConfigMap = {}; export namespace gui { /** 注册界面组件 */ - export function register(key: string, config: UIConfig) { - return function (ctor: any) { - //@ts-ignore - ctor[gui.internal.GUI_KEY] = key; + export function register(key: string, config: UIConfig): (ctor: GameComponentCtor) => void { + return function (ctor: GameComponentCtor): void { + (ctor as any)[gui.internal.GUI_KEY] = key; internal.setConfig(key, config); }; } @@ -19,28 +19,26 @@ export namespace gui { export const GUI_KEY = 'OOPS_GUI_KEY'; /** 获取界面唯一关键字 */ - export function getKey(ctor: any) { - return ctor[GUI_KEY]; + export function getKey(ctor: UICtor): string { + return (ctor as any)[GUI_KEY]; } /** 获取界面组件配置 */ - export function getConfig(key: string) { + export function getConfig(key: string): UIConfig { return configs[key]; } - /** 获取界面组件配置 */ - export function setConfig(key: string, config: UIConfig) { - const c = getConfig(key); - if (c == null) { - configs[key] = config; - } - else { + /** 设置界面组件配置 */ + export function setConfig(key: string, config: UIConfig): void { + if (configs[key] != null) { console.error(`界面${key}重复注册`); + return; } + configs[key] = config; } /** 初始化界面组件配置 */ - export function initConfigs(uicm: UIConfigMap) { + export function initConfigs(uicm: UIConfigMap): void { for (const key in uicm) { configs[key] = uicm[key]; } diff --git a/assets/module/common/CCBusiness.ts b/assets/module/common/CCBusiness.ts index a514bf1..b864f91 100644 --- a/assets/module/common/CCBusiness.ts +++ b/assets/module/common/CCBusiness.ts @@ -21,7 +21,7 @@ export class CCBusiness { destroy() { // 释放消息对象 if (this._event) { - this._event.destroy(); + this._event.clear(); this._event = null; } diff --git a/assets/module/common/CCEntity.ts b/assets/module/common/CCEntity.ts index 212ad8c..8715fbe 100644 --- a/assets/module/common/CCEntity.ts +++ b/assets/module/common/CCEntity.ts @@ -1,37 +1,20 @@ -import type { __private, Node } from 'cc'; -import { resLoader } from '../../core/common/loader/ResLoader'; +import type { Node } from 'cc'; +import { oops } from '../../core/Oops'; import { gui } from '../../core/gui/Gui'; import type { UIParam } from '../../core/gui/layer/LayerUIElement'; import { LayerUIElement } from '../../core/gui/layer/LayerUIElement'; -import { oops } from '../../core/Oops'; import { ViewUtil } from '../../core/utils/ViewUtil'; import { ecs } from '../../libs/ecs/ECS'; import type { ECSEntity } from '../../libs/ecs/ECSEntity'; import type { CompType } from '../../libs/ecs/ECSModel'; +import type { BusinessCtor, EntityCtor, GUIView, UICtor, ViewCtor } from '../types/Types'; import type { CCBusiness } from './CCBusiness'; -import type { CCView } from './CCView'; import { GameComponent } from './GameComponent'; -/** ECS 组件构造函数类型(用于继承自 ecs.Comp 的组件) */ -type ECSCtor = - | __private.__types_globals__Constructor - | __private.__types_globals__AbstractedConstructor; -/** UI 组件构造函数类型(用于继承自 GameComponent 并使用 gui.register 注册的组件) */ -type UICtor = - | __private.__types_globals__Constructor - | __private.__types_globals__AbstractedConstructor; -/** ECS 游戏视图组件类型(继承自 CCView,用于完整的 ECS 组件) */ -export type ECSView = CCView; -/** GUI 视图组件类型(继承自 GameComponent,使用 @gui.register 装饰器注册的组件) */ -export type GUIView = GameComponent; -/** ECS 实体构造函数类型 */ -type EntityCtor = new (...args: any[]) => T; -/** ECS 业务逻辑组件构造函数类型 */ -type BusinessCtor = CCBusiness> = new (...args: any[]) => T; - /** ECS 游戏模块实体 */ export abstract class CCEntity extends ecs.Entity { //#region 子模块管理 + /** 单例子实体集合(key: 实体类构造函数,value: 实体实例) */ private singletons: Map = null!; @@ -94,37 +77,40 @@ export abstract class CCEntity extends ecs.Entity { //#region 游戏视图层管理 /** * 通过资源内存中获取预制上的组件添加到ECS实体中 - * @param ctor 界面逻辑组件 + * @param ctor 界面逻辑组件(支持 ECSView 或使用 gui.register 注册的 GUIView) * @param parent 显示对象父级 - * @param path 显示资源地址 - * @param bundleName 资源包名称 + * @param path 显示资源地址(可选,不传时使用 @game.prefab 装饰器注册的路径) + * @param bundleName 资源包名称(可选,不传时使用 @game.prefab 装饰器注册的包名) */ - async addPrefab( - ctor: ECSCtor, + async addPrefab( + ctor: ViewCtor, parent: Node | GameComponent, - path: string, - bundleName: string = resLoader.defaultBundleName + path?: string, + bundleName?: string ): Promise { + // 未传入路径时,从装饰器注册的数据中获取 + if (path == null) { + path = (ctor as any).GAME_PREFAB_PATH; + bundleName = (ctor as any).GAME_PREFAB_BUNDLE; + if (path == null) { + throw new Error(`组件 ${(ctor as any).name} 未使用 @game.prefab 装饰器注册,请添加 @game.prefab('path/to/prefab') 装饰器或手动传入路径参数`); + } + } + let node: Node; // 跟随父节点释放自动释放当前资源 if (parent instanceof GameComponent) { node = await parent.createPrefabNode(path, bundleName); const comp = node.getComponent(ctor); - if (!comp) { - throw new Error(`组件 ${ctor.name} 不存在于预制 ${path} 中`); - } - this.add(comp); + if (comp) this.add(comp as unknown as ecs.Comp); node.parent = parent.node; } // 手动内存管理 else { node = await ViewUtil.createPrefabNodeAsync(path, bundleName); const comp = node.getComponent(ctor); - if (!comp) { - throw new Error(`组件 ${ctor.name} 不存在于预制 ${path} 中`); - } - this.add(comp); + if (comp) this.add(comp as unknown as ecs.Comp); node.parent = parent; } diff --git a/assets/module/decorator.meta b/assets/module/decorator.meta new file mode 100644 index 0000000..dd9bb0a --- /dev/null +++ b/assets/module/decorator.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "72863800-5c5d-44f0-b86c-a5b0d0643d8f", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/module/decorator/GamePrefabDecorator.ts b/assets/module/decorator/GamePrefabDecorator.ts new file mode 100644 index 0000000..5e86ed5 --- /dev/null +++ b/assets/module/decorator/GamePrefabDecorator.ts @@ -0,0 +1,32 @@ +import { resLoader } from '../../core/common/loader/ResLoader'; +import type { GameComponentCtor } from '../types/Types'; + +/** + * 游戏装饰器命名空间 + */ +export namespace prefab { + /** + * Prefab 装饰器 - 用于标记组件的预制体路径 + * @param path 预制体路径 + * @param bundleName 资源包名称(可选,默认使用 resLoader.defaultBundleName) + * @example + * ```typescript + * @ccclass('V_Backpack_Prop') + * @ecs.register('V_Backpack_Prop', false) + * @prefab.register('gui/backpack/prefab/V_Backpack_Prop') + * export class V_Backpack_Prop extends CCView { + * // ... + * } + * + * // 使用时 + * await entity.addPrefab(V_Backpack_Prop, parentNode); + * ``` + */ + export function register(path: string, bundleName?: string): (ctor: GameComponentCtor) => void { + return function (ctor: GameComponentCtor): void { + const bundle = bundleName || resLoader.defaultBundleName; + (ctor as any).GAME_PREFAB_PATH = path; + (ctor as any).GAME_PREFAB_BUNDLE = bundle; + }; + } +} diff --git a/assets/module/decorator/GamePrefabDecorator.ts.meta b/assets/module/decorator/GamePrefabDecorator.ts.meta new file mode 100644 index 0000000..598f2eb --- /dev/null +++ b/assets/module/decorator/GamePrefabDecorator.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "7c1ef3cb-50c6-49d4-b17c-11b8d37535bf", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/module/decorator/index.ts b/assets/module/decorator/index.ts new file mode 100644 index 0000000..836236d --- /dev/null +++ b/assets/module/decorator/index.ts @@ -0,0 +1,8 @@ +/** + * 游戏装饰器模块 + * 提供各种装饰器用于简化开发 + */ + +export { prefab as game } from './GamePrefabDecorator'; +export type { PrefabConfig as PrefabMetadata } from './GamePrefabDecorator'; + diff --git a/assets/module/decorator/index.ts.meta b/assets/module/decorator/index.ts.meta new file mode 100644 index 0000000..c9e8f7e --- /dev/null +++ b/assets/module/decorator/index.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "7f85c52b-9f8e-4f0d-b749-7dca9fb8f57e", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/module/types.meta b/assets/module/types.meta new file mode 100644 index 0000000..6d36002 --- /dev/null +++ b/assets/module/types.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.2.0", + "importer": "directory", + "imported": true, + "uuid": "050d0e85-32a0-4573-b0ef-d8911bf7f13d", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/module/types/Types.ts b/assets/module/types/Types.ts new file mode 100644 index 0000000..073aecd --- /dev/null +++ b/assets/module/types/Types.ts @@ -0,0 +1,37 @@ +import type { GameComponent } from 'db://oops-framework/module/common/GameComponent'; +import type { ecs } from 'db://oops-framework/libs/ecs/ECS'; +import type { CCView } from 'db://oops-framework/module/common/CCView'; +import type { CCEntity } from 'db://oops-framework/module/common/CCEntity'; +import type { CCBusiness } from 'db://oops-framework/module/common/CCBusiness'; + +// ==================== 通用构造函数类型 ==================== + +/** 通用构造函数类型 */ +type Ctor = new (...args: any[]) => T; + +// ==================== 视图类型 ==================== + +/** GameComponent 及其子类的构造函数类型,用于类型安全的组件实例化 */ +export type GameComponentCtor = Ctor; + +/** UI 组件构造函数类型(用于继承自 GameComponent 并使用 gui.register 注册的组件) */ +export type UICtor = Ctor; + +/** 通用的视图组件构造函数类型(支持 ECSView 或 GUIView) */ +export type ViewCtor = Ctor; + +/** ECS 游戏视图组件类型(继承自 CCView,用于完整的 ECS 组件) */ +export type ECSView = CCView; + +/** GUI 视图组件类型(继承自 GameComponent,使用 @gui.register 装饰器注册的组件) */ +export type GUIView = GameComponent; + +// ==================== 实体类型 ==================== + +/** ECS 实体构造函数类型 */ +export type EntityCtor = Ctor; + +// ==================== 业务逻辑类型 ==================== + +/** ECS 业务逻辑组件构造函数类型 */ +export type BusinessCtor = CCBusiness> = Ctor; diff --git a/assets/module/types/Types.ts.meta b/assets/module/types/Types.ts.meta new file mode 100644 index 0000000..4cf1bd6 --- /dev/null +++ b/assets/module/types/Types.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "4d52b2a4-af0e-4ae4-b575-920ec36b6bcf", + "files": [], + "subMetas": {}, + "userData": {} +} From c5ddb8c1001a18753b5044f2918fd30bb1c5f04c Mon Sep 17 00:00:00 2001 From: dgflash Date: Mon, 23 Feb 2026 17:41:50 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A1=86=E6=9E=B6?= =?UTF-8?q?=E4=B8=AD=E8=87=AA=E5=AE=9A=E4=B9=89=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/core/game/GameManager.ts | 7 ++++--- assets/core/gui/Gui.ts | 2 +- assets/module/common/CCEntity.ts | 23 ++++++++++++++++++----- assets/module/common/CCView.ts | 8 ++++---- assets/{module => }/types.meta | 2 +- assets/{module => }/types/Types.ts | 7 ++++--- assets/{module => }/types/Types.ts.meta | 0 7 files changed, 32 insertions(+), 17 deletions(-) rename assets/{module => }/types.meta (70%) rename assets/{module => }/types/Types.ts (90%) rename assets/{module => }/types/Types.ts.meta (100%) diff --git a/assets/core/game/GameManager.ts b/assets/core/game/GameManager.ts index d8cfabc..3f6a188 100644 --- a/assets/core/game/GameManager.ts +++ b/assets/core/game/GameManager.ts @@ -9,6 +9,7 @@ import { director, isValid } from 'cc'; import { GameComponent } from '../../module/common/GameComponent'; import { resLoader } from '../common/loader/ResLoader'; import { ViewUtil } from '../utils/ViewUtil'; +import { View } from '../../types/Types'; /** 游戏元素打开参数 */ export interface ElementParams { @@ -37,7 +38,7 @@ export class GameManager { * @param params 可选参数据 * @returns Promise 成功返回节点,失败返回 null */ - async open(parent: Node | GameComponent, prefabPath: string, params?: ElementParams): Promise { + async open(parent: View, prefabPath: string, params?: ElementParams): Promise { try { // 简化 bundleName 获取逻辑 const bundleName = params?.bundle || resLoader.defaultBundleName; @@ -61,7 +62,7 @@ export class GameManager { return null; } node.parent = parent; - + // 记录手动管理的节点,便于后续释放 this._manualNodes.add(node); } @@ -144,7 +145,7 @@ export class GameManager { destroy(): void { // 释放所有手动管理的节点 this.releaseAllManualNodes(); - + // 清理引用 this.root = null!; } diff --git a/assets/core/gui/Gui.ts b/assets/core/gui/Gui.ts index 48878a9..03ede4b 100644 --- a/assets/core/gui/Gui.ts +++ b/assets/core/gui/Gui.ts @@ -1,4 +1,4 @@ -import type { GameComponentCtor, UICtor } from '../../module/types/Types'; +import type { GameComponentCtor, UICtor } from '../../types/Types'; import type { UIConfigMap } from './layer/LayerEnum'; import type { UIConfig } from './layer/UIConfig'; diff --git a/assets/module/common/CCEntity.ts b/assets/module/common/CCEntity.ts index 8715fbe..5f25d3a 100644 --- a/assets/module/common/CCEntity.ts +++ b/assets/module/common/CCEntity.ts @@ -7,7 +7,7 @@ import { ViewUtil } from '../../core/utils/ViewUtil'; import { ecs } from '../../libs/ecs/ECS'; import type { ECSEntity } from '../../libs/ecs/ECSEntity'; import type { CompType } from '../../libs/ecs/ECSModel'; -import type { BusinessCtor, EntityCtor, GUIView, UICtor, ViewCtor } from '../types/Types'; +import type { BusinessCtor, EntityCtor, UICtor, View, ViewCtor } from '../../types/Types'; import type { CCBusiness } from './CCBusiness'; import { GameComponent } from './GameComponent'; @@ -77,14 +77,14 @@ export abstract class CCEntity extends ecs.Entity { //#region 游戏视图层管理 /** * 通过资源内存中获取预制上的组件添加到ECS实体中 - * @param ctor 界面逻辑组件(支持 ECSView 或使用 gui.register 注册的 GUIView) + * @param ctor 界面逻辑组件(支持 ECSView 或使用 gui.register 注册的 GameComponent) * @param parent 显示对象父级 * @param path 显示资源地址(可选,不传时使用 @game.prefab 装饰器注册的路径) * @param bundleName 资源包名称(可选,不传时使用 @game.prefab 装饰器注册的包名) */ - async addPrefab( + async addPrefab( ctor: ViewCtor, - parent: Node | GameComponent, + parent: View, path?: string, bundleName?: string ): Promise { @@ -117,13 +117,26 @@ export abstract class CCEntity extends ecs.Entity { return node; } + /** + * 移除预制体组件及其节点 + * @param node 要销毁的节点或组件(Node 或 GameComponent) + */ + removePrefab(node: View): void { + if (node instanceof GameComponent) { + node.remove(); + } + else { + node.destroy(); + } + } + /** * 添加视图层组件 * @param ctor 界面逻辑组件(支持 CCView 或使用 gui.register 注册的 GameComponent 子类) * @param params 界面参数 * @returns 界面节点 */ - async addUi(ctor: UICtor, params?: UIParam): Promise { + async addUi(ctor: UICtor, params?: UIParam): Promise { const key = gui.internal.getKey(ctor); if (!key) { throw new Error(`${ctor.name} 界面组件未使用 gui.register 注册`); diff --git a/assets/module/common/CCView.ts b/assets/module/common/CCView.ts index f8f67ad..06aa6db 100644 --- a/assets/module/common/CCView.ts +++ b/assets/module/common/CCView.ts @@ -11,6 +11,7 @@ import { ECSModel } from '../../libs/ecs/ECSModel'; import { VM } from '../../libs/model-view/ViewModel'; import { VMBase } from '../../libs/model-view/VMBase'; import type { CCEntity } from './CCEntity'; +import type { UICtor } from '../../types/Types'; import { GameComponent } from './GameComponent'; /** @@ -83,7 +84,6 @@ export abstract class CCView extends GameComponent implement * 注意:子类应该显式初始化此属性 */ protected data?: any; - //#endregion /** * 组件加载时调用 @@ -140,7 +140,7 @@ export abstract class CCView extends GameComponent implement * @private */ private replaceVMPath(comp: Component, tag: string) { - // @ts-ignore - 优化:使用 any 类型避免多次类型转换 + // 优化:使用 any 类型避免多次类型转换 const vmComp: any = comp; const path: string = vmComp.watchPath; @@ -222,6 +222,7 @@ export abstract class CCView extends GameComponent implement return result; } + //#endregion /** 从父节点移除自己 */ remove() { @@ -241,7 +242,7 @@ export abstract class CCView extends GameComponent implement return; } - this.ent.removeUi(cct); + this.ent.removeUi(cct as unknown as UICtor); this.ent = null!; // 清空引用,避免内存泄漏 } @@ -257,7 +258,6 @@ export abstract class CCView extends GameComponent implement // 解除全部引用 if (this.tag) { VM.remove(this.tag); - // @ts-ignore - 优化:显式清空引用,帮助 GC this.tag = undefined; } diff --git a/assets/module/types.meta b/assets/types.meta similarity index 70% rename from assets/module/types.meta rename to assets/types.meta index 6d36002..7295582 100644 --- a/assets/module/types.meta +++ b/assets/types.meta @@ -2,7 +2,7 @@ "ver": "1.2.0", "importer": "directory", "imported": true, - "uuid": "050d0e85-32a0-4573-b0ef-d8911bf7f13d", + "uuid": "172d5432-5689-483f-8ede-94d9fdf5c747", "files": [], "subMetas": {}, "userData": {} diff --git a/assets/module/types/Types.ts b/assets/types/Types.ts similarity index 90% rename from assets/module/types/Types.ts rename to assets/types/Types.ts index 073aecd..590b202 100644 --- a/assets/module/types/Types.ts +++ b/assets/types/Types.ts @@ -1,3 +1,4 @@ +import type { Node } from 'cc'; import type { GameComponent } from 'db://oops-framework/module/common/GameComponent'; import type { ecs } from 'db://oops-framework/libs/ecs/ECS'; import type { CCView } from 'db://oops-framework/module/common/CCView'; @@ -17,14 +18,14 @@ export type GameComponentCtor = Ctor /** UI 组件构造函数类型(用于继承自 GameComponent 并使用 gui.register 注册的组件) */ export type UICtor = Ctor; -/** 通用的视图组件构造函数类型(支持 ECSView 或 GUIView) */ +/** 通用的视图组件构造函数类型(支持 ECSView 或 GameComponent) */ export type ViewCtor = Ctor; /** ECS 游戏视图组件类型(继承自 CCView,用于完整的 ECS 组件) */ export type ECSView = CCView; -/** GUI 视图组件类型(继承自 GameComponent,使用 @gui.register 装饰器注册的组件) */ -export type GUIView = GameComponent; +/** 视图节点类型(Node 或 GameComponent) */ +export type View = Node | GameComponent; // ==================== 实体类型 ==================== diff --git a/assets/module/types/Types.ts.meta b/assets/types/Types.ts.meta similarity index 100% rename from assets/module/types/Types.ts.meta rename to assets/types/Types.ts.meta From 500f7dcb1c723f8d69df578ad831f9bcda03a66d Mon Sep 17 00:00:00 2001 From: dgflash Date: Mon, 23 Feb 2026 20:12:35 +0800 Subject: [PATCH 7/7] . --- assets/module/decorator/GamePrefabDecorator.ts | 2 +- assets/module/decorator/index.ts | 8 -------- assets/module/decorator/index.ts.meta | 9 --------- 3 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 assets/module/decorator/index.ts delete mode 100644 assets/module/decorator/index.ts.meta diff --git a/assets/module/decorator/GamePrefabDecorator.ts b/assets/module/decorator/GamePrefabDecorator.ts index 5e86ed5..4a00495 100644 --- a/assets/module/decorator/GamePrefabDecorator.ts +++ b/assets/module/decorator/GamePrefabDecorator.ts @@ -1,5 +1,5 @@ import { resLoader } from '../../core/common/loader/ResLoader'; -import type { GameComponentCtor } from '../types/Types'; +import { GameComponentCtor } from '../../types/Types'; /** * 游戏装饰器命名空间 diff --git a/assets/module/decorator/index.ts b/assets/module/decorator/index.ts deleted file mode 100644 index 836236d..0000000 --- a/assets/module/decorator/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * 游戏装饰器模块 - * 提供各种装饰器用于简化开发 - */ - -export { prefab as game } from './GamePrefabDecorator'; -export type { PrefabConfig as PrefabMetadata } from './GamePrefabDecorator'; - diff --git a/assets/module/decorator/index.ts.meta b/assets/module/decorator/index.ts.meta deleted file mode 100644 index c9e8f7e..0000000 --- a/assets/module/decorator/index.ts.meta +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ver": "4.0.24", - "importer": "typescript", - "imported": true, - "uuid": "7f85c52b-9f8e-4f0d-b749-7dca9fb8f57e", - "files": [], - "subMetas": {}, - "userData": {} -}