# 基于Oops Plugin Framework的资源管理系统 **本文档引用的文件** - [loader.md](file://doc/core/common/loader.md) - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts) - [Initialize.ts](file://assets/script/game/initialize/Initialize.ts) - [LoadingViewComp.ts](file://assets/script/game/initialize/view/LoadingViewComp.ts) - [GameMap.ts](file://assets/script/game/map/GameMap.ts) - [HeroSpine.ts](file://assets/script/game/hero/HeroSpine.ts) - [HeroAnmComp.ts](file://assets/script/game/hero/HeroAnmComp.ts) - [config.json](file://assets/resources/config.json) - [builder.json](file://settings/v2/packages/builder.json) - [engine.json](file://settings/v2/packages/engine.json) ## 目录 1. [简介](#简介) 2. [系统架构](#系统架构) 3. [核心API详解](#核心api详解) 4. [资源加载场景](#资源加载场景) 5. [Bundle管理策略](#bundle管理策略) 6. [性能优化技术](#性能优化技术) 7. [内存管理机制](#内存管理机制) 8. [调试与监控](#调试与监控) 9. [常见问题与解决方案](#常见问题与解决方案) 10. [最佳实践指南](#最佳实践指南) ## 简介 Oops Plugin Framework的资源管理系统是一个功能强大的资源加载与管理框架,专为Cocos Creator游戏开发而设计。该系统提供了完整的资源生命周期管理,包括加载、缓存、释放和监控等功能,支持Spine骨骼动画、SpriteFrame、配置文件等多种资源类型的高效管理。 ### 核心特性 - **多格式支持**:支持Spine骨骼动画、SpriteFrame、音频、纹理等多种资源类型 - **智能缓存**:自动管理资源缓存,避免重复加载 - **异步加载**:支持进度回调和错误处理 - **Bundle管理**:灵活的资源包划分和热更新支持 - **内存优化**:自动垃圾回收和手动释放机制 - **调试工具**:内置资源状态监控和调试功能 ## 系统架构 ```mermaid graph TB subgraph "资源管理层" ResLoader[ResLoader 核心类] AssetManager[AssetManager 引擎层] BundleManager[Bundle管理器] end subgraph "资源类型" SpineRes[Spine骨骼动画] SpriteRes[SpriteFrame精灵图] ConfigRes[配置文件] AudioRes[音频资源] end subgraph "加载策略" SyncLoad[同步加载] AsyncLoad[异步加载] BatchLoad[批量加载] LazyLoad[延迟加载] end subgraph "内存管理" RefCount[引用计数] AutoGC[自动垃圾回收] ManualRelease[手动释放] end ResLoader --> AssetManager ResLoader --> BundleManager AssetManager --> SpineRes AssetManager --> SpriteRes AssetManager --> ConfigRes AssetManager --> AudioRes ResLoader --> SyncLoad ResLoader --> AsyncLoad ResLoader --> BatchLoad ResLoader --> LazyLoad ResLoader --> RefCount RefCount --> AutoGC RefCount --> ManualRelease ``` **图表来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L22-L335) **章节来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L1-L50) ## 核心API详解 ### load - 单资源加载 `load`方法是最基础的资源加载API,支持多种重载形式以适应不同的使用场景。 #### 基本语法 ```typescript // 加载默认Bundle中的资源 oops.res.load(path, type, onComplete); // 加载指定Bundle中的资源 oops.res.load(bundleName, path, type, onComplete); // 支持进度回调 oops.res.load(path, type, onProgress, onComplete); ``` #### 参数说明 | 参数 | 类型 | 描述 | |------|------|------| | `bundleName` | `string` | Bundle名称,默认为"resources" | | `paths` | `string \| string[]` | 资源路径或路径数组 | | `type` | `AssetType` | 资源类型构造函数 | | `onProgress` | `ProgressCallback` | 加载进度回调函数 | | `onComplete` | `CompleteCallback` | 加载完成回调函数 | #### 使用示例 **加载Spine骨骼动画:** ```typescript // 加载角色动画资源 oops.res.load("hero/skeleton", sp.SkeletonData, (err, skeletonData) => { if (err) { console.error("角色动画加载失败:", err); return; } this.skeletonData = skeletonData; this.updateSkeleton(); }); ``` **加载SpriteFrame:** ```typescript // 加载UI按钮图片 oops.res.load("ui/button_normal", SpriteFrame, (err, spriteFrame) => { if (err) { console.error("按钮图片加载失败:", err); return; } this.button.spriteFrame = spriteFrame; }); ``` ### loadDir - 文件夹批量加载 `loadDir`方法用于批量加载指定目录下的所有资源,特别适合加载同一类型的资源集合。 #### 语法结构 ```typescript oops.res.loadDir(bundleName, dir, type, onProgress, onComplete); ``` #### 进度回调机制 ```mermaid sequenceDiagram participant Client as 客户端 participant ResLoader as ResLoader participant AssetManager as AssetManager participant Progress as 进度回调 Client->>ResLoader : loadDir("game", onProgress, onComplete) ResLoader->>AssetManager : loadDir(path, type, progress, complete) AssetManager->>AssetManager : 开始扫描目录 loop 每个资源文件 AssetManager->>Progress : onProgress(finished, total, item) Progress->>Client : 更新UI进度条 end AssetManager->>ResLoader : 加载完成 ResLoader->>Client : onComplete(resources[]) ``` **图表来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L150-L200) - [LoadingViewComp.ts](file://assets/script/game/initialize/view/LoadingViewComp.ts#L70-L90) #### 实际应用案例 **游戏资源加载流程:** ```typescript // 加载游戏主界面资源 oops.res.loadDir("game", (finished, total, item) => { // 更新加载进度 this.updateProgressBar(finished, total); console.log(`正在加载: ${item.uuid} (${finished}/${total})`); }, () => { // 所有资源加载完成 this.onGameResourcesLoaded(); }); ``` **章节来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L150-L220) - [LoadingViewComp.ts](file://assets/script/game/initialize/view/LoadingViewComp.ts#L60-L90) ### loadRemote - 远程资源加载 `loadRemote`方法专门用于加载网络上的远程资源,支持图片、音频等文件的动态下载。 #### 方法签名 ```typescript oops.res.loadRemote(url: string, options: IRemoteOptions, onComplete: CompleteCallback); ``` #### 配置选项 | 选项 | 类型 | 描述 | |------|------|------| | `ext` | `string` | 文件扩展名,如".png"、".mp3" | | `timeout` | `number` | 请求超时时间(毫秒) | | `headers` | `Record` | 自定义请求头 | #### 完整实现示例 ```typescript // 加载远程图片资源 const options: IRemoteOptions = { ext: ".png", timeout: 10000 }; oops.res.loadRemote(imageUrl, options, (err, imageAsset) => { if (err) { console.error("远程图片加载失败:", err); return; } // 创建纹理和SpriteFrame const texture = new Texture2D(); texture.image = imageAsset; const spriteFrame = new SpriteFrame(); spriteFrame.texture = texture; // 应用到UI元素 this.avatarSprite.spriteFrame = spriteFrame; }); ``` ### loadBundle - 资源包加载 `loadBundle`方法支持从服务器动态加载资源包,实现游戏的热更新功能。 #### 使用流程 ```mermaid flowchart TD Start([开始热更新]) --> CheckVersion{检查版本} CheckVersion --> |需要更新| LoadBundle[加载远程Bundle] CheckVersion --> |无需更新| UseLocal[使用本地资源] LoadBundle --> VerifyMD5{验证MD5} VerifyMD5 --> |验证成功| ReplaceAssets[替换旧资源] VerifyMD5 --> |验证失败| RetryDownload[重试下载] ReplaceAssets --> UpdateCache[更新缓存] UpdateCache --> Complete([更新完成]) RetryDownload --> MaxRetry{达到最大重试次数?} MaxRetry --> |否| LoadBundle MaxRetry --> |是| FallbackLocal[回退到本地] FallbackLocal --> Complete UseLocal --> Complete ``` **图表来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L60-L80) #### 热更新实现 ```typescript // 热更新资源包 async function hotUpdate() { const serverUrl = "http://192.168.1.13:8082/"; const md5 = "8e5c0"; // 构建后的MD5字符 try { // 加载远程资源包 const bundle = await oops.res.loadBundle(serverUrl, md5); console.log("热更新包加载成功:", bundle.name); // 更新资源引用 this.updateResourceReferences(); // 清理旧资源 oops.res.releaseDir("", "resources"); } catch (error) { console.error("热更新失败:", error); // 回退到本地资源 this.useLocalResources(); } } ``` **章节来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L60-L85) ### release & releaseDir - 资源释放 资源释放是内存管理的关键环节,系统提供了精确的资源释放机制。 #### release - 单资源释放 ```typescript // 释放单个资源 oops.res.release("model/player", "resources"); // 释放其他Bundle中的资源 oops.res.release("ui/button", "assets"); ``` #### releaseDir - 批量资源释放 ```typescript // 释放整个目录的资源 oops.res.releaseDir("game", "resources"); // 释放后清理Bundle(如果需要) oops.res.releaseDir("temp", "temporary"); ``` #### 内存管理原理 ```mermaid classDiagram class Asset { +number refCount +decRef() void +addRef() void +destroy() void } class ResLoader { +release(path, bundle) void +releaseDir(path, bundle) void -releasePrefabtDepsRecursively(uuid) void } class AssetManager { +assets Map~string,Asset~ +releaseAsset(asset) void +getBundle(name) Bundle } class Bundle { +get(path, type) Asset +getDirWithPath(path) AssetInfo[] +remove() void } ResLoader --> AssetManager AssetManager --> Asset AssetManager --> Bundle ResLoader --> Bundle ``` **图表来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L240-L280) **章节来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L240-L300) ## 资源加载场景 ### Spine骨骼动画加载 Spine骨骼动画是游戏中常见的动画表现形式,系统提供了专门的加载和管理机制。 #### 加载流程 ```mermaid sequenceDiagram participant Game as 游戏逻辑 participant ResLoader as 资源加载器 participant SpineManager as Spine管理器 participant Skeleton as 骨骼实例 Game->>ResLoader : load("spine/hero", SkeletonData) ResLoader->>ResLoader : 查找缓存资源 alt 缓存命中 ResLoader-->>Game : 返回缓存资源 else 缓存未命中 ResLoader->>ResLoader : 下载并解析资源 ResLoader-->>Game : 返回新资源 end Game->>SpineManager : 创建骨骼实例 SpineManager->>Skeleton : 设置SkeletonData Skeleton-->>SpineManager : 骨骼实例就绪 SpineManager-->>Game : 动画控制器就绪 ``` **图表来源** - [HeroSpine.ts](file://assets/script/game/hero/HeroSpine.ts#L1-L50) - [HeroAnmComp.ts](file://assets/script/game/hero/HeroAnmComp.ts#L1-L30) #### 实际应用代码 **HeroSpine组件实现:** ```typescript // 在HeroSpine组件中加载动画资源 protected initAnimator() { // 加载Spine骨骼数据 oops.res.load("hero/skeleton", sp.SkeletonData, (err, skeletonData) => { if (err) { console.error("骨骼数据加载失败:", err); return; } // 设置骨骼数据 this.skeleton.skeletonData = skeletonData; // 初始化动画组件 this.setupAnimationController(); }); } // 动画状态切换 playAnimation(name: string) { // 检查动画是否正在播放 if (this.skeleton.animation === name && this.skeleton.isAnimationCached(name)) { return; } // 播放指定动画 this.skeleton.animation = name; this.skeleton.setAnimation(0, name, true); } ``` **HeroAnmComp动画控制器:** ```typescript // 动画播放控制 idle() { if (this.anmcon.getState("idle").isPlaying) return; this.anmcon.play("idle"); this.default_anim = 'idle'; } atk() { if (this.anmcon.getState("atk0").isPlaying) return; this.anmcon.play("atk0"); } move() { if (this.anmcon.getState("move").isPlaying) return; this.anmcon.play("move"); this.default_anim = 'move'; } ``` **章节来源** - [HeroSpine.ts](file://assets/script/game/hero/HeroSpine.ts#L20-L80) - [HeroAnmComp.ts](file://assets/script/game/hero/HeroAnmComp.ts#L20-L50) ### SpriteFrame精灵图加载 SpriteFrame是UI和游戏元素的基础资源类型,系统提供了高效的加载和管理机制。 #### 加载策略 | 场景 | 推荐策略 | 示例 | |------|----------|------| | UI按钮 | 同步加载 | `oops.res.load("ui/button", SpriteFrame)` | | 背景图片 | 异步加载 | `oops.res.loadDir("background", ...)` | | 角色皮肤 | 延迟加载 | 游戏开始后按需加载 | | 动态图标 | 缓存复用 | 首次加载后缓存 | #### 实际应用 ```typescript // UI按钮状态管理 class ButtonStateManager { private buttonStates: Map = new Map(); async loadButtonStates() { const states = ['normal', 'hover', 'pressed', 'disabled']; // 并行加载所有状态 await Promise.all(states.map(async (state) => { const spriteFrame = await oops.res.loadAsync( "ui/button_" + state, SpriteFrame ); this.buttonStates.set(state, spriteFrame); })); } setButtonState(button: Button, state: string) { const spriteFrame = this.buttonStates.get(state); if (spriteFrame) { button.spriteFrame = spriteFrame; } } } ``` ### 配置文件加载 配置文件是游戏运行时的重要数据源,系统提供了灵活的配置加载机制。 #### 配置文件组织结构 ```mermaid graph TD ConfigRoot[配置根目录] --> GameConfig[game/] ConfigRoot --> MapConfig[map/] ConfigRoot --> LanguageConfig[language/] GameConfig --> NetCode[NetCode.json] GameConfig --> GameUI[GameUIConfig.ts] MapConfig --> MapData[map.json] MapConfig --> DeliveryData[map_delivery.json] LanguageConfig --> EnLang[en.json] LanguageConfig --> ZhLang[zh.json] ``` **图表来源** - [config.json](file://assets/resources/config.json#L1-L21) #### 加载实现 ```typescript // 配置文件加载器 class ConfigLoader { private configs: Map = new Map(); async loadGameConfig() { // 加载网络代码配置 const netCode = await this.loadJsonConfig("game/NetCode"); this.configs.set("netCode", netCode); // 加载UI配置 const uiConfig = await this.loadJsonConfig("game/GameUIConfig"); this.configs.set("ui", uiConfig); // 加载地图配置 const mapConfigs = await Promise.all([ this.loadJsonConfig("map/map"), this.loadJsonConfig("map/map_delivery") ]); this.configs.set("map", mapConfigs); } private async loadJsonConfig(path: string): Promise { return new Promise((resolve, reject) => { oops.res.load(path, JsonAsset, (err, jsonAsset) => { if (err) { reject(err); return; } resolve(JSON.parse(jsonAsset.json)); }); }); } } ``` **章节来源** - [config.json](file://assets/resources/config.json#L1-L21) - [Initialize.ts](file://assets/script/game/initialize/Initialize.ts#L40-L70) ## Bundle管理策略 ### Bundle划分原则 合理的Bundle划分是资源管理的基础,遵循以下原则: #### 1. 功能模块划分 ```mermaid graph LR Resources[resources] --> Common[公共资源] Resources --> Game[游戏资源] Resources --> UI[界面资源] Resources --> Audio[音频资源] Common --> Fonts[字体文件] Common --> Shaders[着色器] Game --> Heroes[英雄资源] Game --> Maps[地图资源] Game --> Skills[技能资源] UI --> Icons[图标资源] UI --> Animations[动画资源] Audio --> Music[背景音乐] Audio --> Effects[音效] ``` #### 2. 生命周期划分 - **常驻Bundle**:游戏启动必需的核心资源 - **场景Bundle**:特定场景的资源包 - **临时Bundle**:临时使用的资源包 - **更新Bundle**:可热更新的资源包 #### 3. 加载优先级划分 ```typescript // 高优先级资源(立即加载) const highPriorityBundles = [ "resources", // 核心引擎资源 "common", // 公共组件资源 "ui/loading" // 加载界面资源 ]; // 中优先级资源(预加载) const mediumPriorityBundles = [ "game", // 游戏主资源 "language" // 语言包资源 ]; // 低优先级资源(延迟加载) const lowPriorityBundles = [ "heroes", // 英雄资源 "skills" // 技能资源 ]; ``` ### Bundle配置管理 #### 构建配置 在`builder.json`中配置Bundle压缩和分发策略: ```json { "bundleConfig": { "custom": { "auto_98AwgBoURL0KsT0uXAsTE4": { "displayName": "resources", "configs": { "native": { "preferredOptions": { "compressionType": "merge_dep", "isRemote": false } }, "wechatgame": { "compressionType": "subpackage", "isRemote": false } } } } } } ``` #### 运行时Bundle管理 ```typescript class BundleManager { private loadedBundles: Set = new Set(); // 按优先级加载Bundle async loadBundlesInOrder(priorityList: string[]) { for (const bundleName of priorityList) { if (!this.loadedBundles.has(bundleName)) { await this.loadBundle(bundleName); this.loadedBundles.add(bundleName); } } } // 条件加载Bundle async conditionallyLoadBundle(condition: boolean, bundleName: string) { if (condition && !this.loadedBundles.has(bundleName)) { await this.loadBundle(bundleName); this.loadedBundles.add(bundleName); } } // 释放Bundle releaseBundle(bundleName: string) { oops.res.releaseDir("", bundleName); this.loadedBundles.delete(bundleName); } } ``` **章节来源** - [builder.json](file://settings/v2/packages/builder.json#L1-L84) - [Initialize.ts](file://assets/script/game/initialize/Initialize.ts#L80-L105) ## 性能优化技术 ### 批量加载优化 #### 并行加载策略 ```typescript // 使用Promise.all进行并行加载 async loadMultipleResources() { const tasks = [ oops.res.loadAsync("hero/skeleton", sp.SkeletonData), oops.res.loadAsync("hero/idle", AnimationClip), oops.res.loadAsync("hero/walk", AnimationClip), oops.res.loadAsync("hero/atk", AnimationClip) ]; try { const [skeletonData, idleClip, walkClip, atkClip] = await Promise.all(tasks); // 批量设置资源 this.setupCharacterAnimations(skeletonData, { idle: idleClip, walk: walkClip, attack: atkClip }); } catch (error) { console.error("批量加载失败:", error); } } ``` #### 分批加载策略 ```typescript class BatchLoader { private readonly BATCH_SIZE = 10; private readonly LOAD_DELAY = 100; async loadResourcesInBatches(resourcePaths: string[]) { const batches = this.chunkArray(resourcePaths, this.BATCH_SIZE); for (let i = 0; i < batches.length; i++) { const batch = batches[i]; // 并行加载当前批次 await Promise.all(batch.map(path => oops.res.loadAsync(path, Asset) )); // 可选:添加延迟避免卡顿 if (i < batches.length - 1) { await this.delay(this.LOAD_DELAY); } } } private chunkArray(array: T[], chunkSize: number): T[][] { const chunks = []; for (let i = 0; i < array.length; i += chunkSize) { chunks.push(array.slice(i, i + chunkSize)); } return chunks; } private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } } ``` ### 延迟加载策略 #### 按需加载实现 ```typescript class LazyResourceManager { private loadedResources: Map = new Map(); private loadingPromises: Map> = new Map(); // 按需加载资源 async getResource(path: string, type: AssetType): Promise { // 检查缓存 const cached = this.loadedResources.get(path); if (cached) { return cached as T; } // 检查是否正在加载 const loadingPromise = this.loadingPromises.get(path); if (loadingPromise) { return loadingPromise.then(asset => asset as T); } // 开始加载 const promise = new Promise((resolve, reject) => { oops.res.load(path, type, (err, asset) => { if (err) { reject(err); return; } this.loadedResources.set(path, asset); this.loadingPromises.delete(path); resolve(asset as T); }); }); this.loadingPromises.set(path, promise); return promise; } // 预加载资源 preloadResources(paths: string[]) { paths.forEach(path => { if (!this.loadedResources.has(path) && !this.loadingPromises.has(path)) { this.getResource(path, Asset); } }); } } ``` ### 内存预分配优化 ```typescript class MemoryOptimizer { private static readonly MEMORY_POOL_SIZE = 1024 * 1024 * 50; // 50MB private memoryPool: ArrayBuffer[] = []; // 预分配内存池 preallocateMemory() { const chunkSize = 1024 * 1024; // 1MB const chunksNeeded = this.MEMORY_POOL_SIZE / chunkSize; for (let i = 0; i < chunksNeeded; i++) { this.memoryPool.push(new ArrayBuffer(chunkSize)); } } // 获取内存块 getMemoryChunk(size: number): ArrayBuffer { const index = Math.floor(Math.random() * this.memoryPool.length); const chunk = this.memoryPool[index]; // 如果大小合适,直接返回 if (chunk.byteLength >= size) { return chunk; } // 否则创建新的内存块 return new ArrayBuffer(size); } // 释放内存 releaseMemory(chunk: ArrayBuffer) { // 可以选择将内存块放回池中 // 或者让JavaScript垃圾回收器处理 } } ``` **章节来源** - [LoadingViewComp.ts](file://assets/script/game/initialize/view/LoadingViewComp.ts#L60-L90) ## 内存管理机制 ### 引用计数系统 系统采用自动引用计数机制管理资源生命周期。 #### 引用计数原理 ```mermaid stateDiagram-v2 [*] --> Created : 创建资源 Created --> Referenced : addRef() Referenced --> Referenced : addRef() Referenced --> Released : decRef() Released --> [*] : refCount === 0 Referenced --> Destroyed : destroy() Destroyed --> [*] : 清理资源 ``` #### 自动释放机制 ```typescript class ResourceManager { private assets: Map = new Map(); private refCounts: Map = new Map(); // 加载资源并增加引用计数 loadWithRefCount(path: string, type: AssetType): T { let asset = this.assets.get(path); if (!asset) { // 首次加载 asset = this.createAsset(path, type); this.assets.set(path, asset); this.refCounts.set(path, 1); } else { // 已存在,增加引用计数 const count = this.refCounts.get(path) || 0; this.refCounts.set(path, count + 1); } return asset; } // 减少引用计数 releaseWithRefCount(path: string) { const count = this.refCounts.get(path) || 0; if (count <= 1) { // 最后一个引用,释放资源 this.assets.delete(path); this.refCounts.delete(path); } else { this.refCounts.set(path, count - 1); } } // 手动释放所有资源 releaseAll() { this.assets.clear(); this.refCounts.clear(); } } ``` ### 垃圾回收策略 #### 自动垃圾回收 ```typescript class GarbageCollector { private readonly MAX_CACHE_SIZE = 1000; private readonly CLEANUP_INTERVAL = 60000; // 1分钟 constructor() { setInterval(() => this.cleanup(), this.CLEANUP_INTERVAL); } // 清理过期资源 cleanup() { const now = Date.now(); const expiredKeys: string[] = []; // 检查缓存大小 if (this.cache.size > this.MAX_CACHE_SIZE) { // 清理最久未使用的资源 const sortedEntries = Array.from(this.cache.entries()) .sort((a, b) => a[1].lastAccess - b[1].lastAccess); const itemsToRemove = sortedEntries.slice(0, 100); itemsToRemove.forEach(([key]) => { this.cache.delete(key); }); } } } ``` ### 手动释放策略 #### 场景化释放 ```typescript class SceneResourceManager { private sceneResources: Set = new Set(); // 记录场景资源 recordSceneResource(path: string) { this.sceneResources.add(path); } // 场景切换时释放资源 releaseSceneResources() { this.sceneResources.forEach(path => { oops.res.release(path); }); this.sceneResources.clear(); } // 批量释放 releaseBatch(paths: string[]) { paths.forEach(path => { oops.res.release(path); this.sceneResources.delete(path); }); } } ``` **章节来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L280-L320) ## 调试与监控 ### 资源状态监控 系统提供了完整的资源状态监控和调试功能。 #### dump方法使用 ```typescript // 打印所有缓存资源信息 oops.res.dump(); // 输出示例: // Asset {uuid: "123456", nativeUrl: "assets/resources/ui/button.png"} // Asset {uuid: "789012", nativeUrl: "assets/resources/audio/bg_music.mp3"} // 当前资源总数:156 ``` #### 资源统计工具 ```typescript class ResourceMonitor { private statistics: Map = new Map(); // 记录资源加载 recordLoad(path: string, type: string, size: number) { const stats = this.statistics.get(path) || { loads: 0, hits: 0, misses: 0, totalSize: 0, lastAccess: 0 }; stats.loads++; stats.totalSize += size; stats.lastAccess = Date.now(); this.statistics.set(path, stats); } // 记录缓存命中 recordHit(path: string) { const stats = this.statistics.get(path); if (stats) { stats.hits++; } } // 获取统计报告 getReport(): ResourceReport { const totalResources = this.statistics.size; let totalSize = 0; let totalLoads = 0; this.statistics.forEach(stats => { totalSize += stats.totalSize; totalLoads += stats.loads; }); return { totalResources, totalSize, hitRate: totalLoads > 0 ? totalLoads / this.statistics.size : 0, topResources: this.getTopResources(), memoryUsage: this.getMemoryUsage() }; } private getTopResources(): TopResource[] { return Array.from(this.statistics.entries()) .sort((a, b) => b[1].loads - a[1].loads) .slice(0, 10) .map(([path, stats]) => ({ path, ...stats })); } } ``` ### 性能监控 #### 加载性能分析 ```typescript class PerformanceMonitor { private loadTimes: Map = new Map(); // 开始测量加载时间 startMeasure(path: string): () => void { const startTime = performance.now(); return () => { const endTime = performance.now(); const duration = endTime - startTime; let times = this.loadTimes.get(path) || []; times.push(duration); if (times.length > 10) { times.shift(); // 保留最近10次记录 } this.loadTimes.set(path, times); }; } // 获取平均加载时间 getAverageLoadTime(path: string): number { const times = this.loadTimes.get(path); if (!times || times.length === 0) { return 0; } return times.reduce((sum, time) => sum + time, 0) / times.length; } // 性能警告 checkPerformanceWarnings() { this.loadTimes.forEach((times, path) => { const avgTime = times.reduce((sum, time) => sum + time, 0) / times.length; if (avgTime > 1000) { // 1秒以上 console.warn(`资源加载缓慢: ${path}, 平均耗时: ${avgTime.toFixed(2)}ms`); } }); } } ``` **章节来源** - [loader.md](file://doc/core/common/loader.md#L86-L90) ## 常见问题与解决方案 ### 资源未释放导致内存溢出 #### 问题描述 资源加载后没有及时释放,导致内存持续增长,最终引发内存溢出。 #### 解决方案 ```typescript // 错误示例:忘记释放资源 class BadResourceManager { loadManyResources() { for (let i = 0; i < 1000; i++) { oops.res.load(`resource_${i}`, Asset, (err, asset) => { if (err) return; // 忘记释放资源! this.processAsset(asset); }); } } } // 正确示例:及时释放资源 class GoodResourceManager { private loadedAssets: Set = new Set(); loadManyResources() { for (let i = 0; i < 1000; i++) { const path = `resource_${i}`; oops.res.load(path, Asset, (err, asset) => { if (err) return; this.processAsset(asset); this.loadedAssets.add(path); }); } } unloadResources() { this.loadedAssets.forEach(path => { oops.res.release(path); }); this.loadedAssets.clear(); } } ``` #### 自动化资源管理 ```typescript class AutoResourceManager { private resourceTracker: Map = new Map(); // 自动跟踪资源使用 trackResourceUsage(path: string) { const info = this.resourceTracker.get(path) || { refCount: 0, lastAccess: Date.now() }; info.refCount++; info.lastAccess = Date.now(); this.resourceTracker.set(path, info); } // 定期清理未使用的资源 cleanupUnusedResources() { const now = Date.now(); const cleanupThreshold = 300000; // 5分钟 this.resourceTracker.forEach((info, path) => { if (info.refCount === 0 && (now - info.lastAccess) > cleanupThreshold) { oops.res.release(path); this.resourceTracker.delete(path); } }); } } ``` ### 异步加载顺序错乱 #### 问题描述 多个异步加载任务同时进行,导致资源加载完成后处理顺序混乱。 #### 解决方案 ```typescript // 错误示例:异步顺序错乱 class BadAsyncLoader { loadCharacterAssets() { oops.res.load("hero/skeleton", SkeletonData, this.handleSkeleton); oops.res.load("hero/idle", AnimationClip, this.handleIdle); oops.res.load("hero/walk", AnimationClip, this.handleWalk); // 处理顺序不确定 } } // 正确示例:使用Promise链 class GoodAsyncLoader { async loadCharacterAssets() { try { const skeletonData = await oops.res.loadAsync("hero/skeleton", SkeletonData); const idleClip = await oops.res.loadAsync("hero/idle", AnimationClip); const walkClip = await oops.res.loadAsync("hero/walk", AnimationClip); // 确保顺序执行 this.setupCharacterAnimations(skeletonData, idleClip, walkClip); } catch (error) { console.error("角色资源加载失败:", error); } } } // 另一种解决方案:使用队列 class QueueAsyncLoader { private loadQueue: LoadTask[] = []; enqueueLoad(path: string, type: AssetType, callback: CompleteCallback) { this.loadQueue.push({path, type, callback}); this.processQueue(); } private processQueue() { if (this.loadQueue.length === 0) return; const task = this.loadQueue[0]; oops.res.load(task.path, task.type, (err, asset) => { task.callback(err, asset); this.loadQueue.shift(); this.processQueue(); }); } } ``` ### Bundle加载失败 #### 问题诊断 ```typescript class BundleErrorHandler { async loadBundleWithRetry(url: string, md5: string, maxRetries: number = 3) { let retries = 0; while (retries < maxRetries) { try { const bundle = await oops.res.loadBundle(url, md5); console.log(`Bundle加载成功: ${bundle.name}`); return bundle; } catch (error) { retries++; console.warn(`Bundle加载失败,重试${retries}/${maxRetries}:`, error); if (retries >= maxRetries) { console.error("Bundle加载多次失败,使用本地资源"); return this.fallbackToLocalBundle(); } // 等待后重试 await this.delay(2000 * retries); } } } private fallbackToLocalBundle() { // 回退到本地Bundle return oops.res.loadBundle("assets/resources"); } private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } } ``` ### 资源路径管理问题 #### 路径规范化 ```typescript class PathManager { // 规范化路径 normalizePath(path: string): string { // 移除开头和结尾的斜杠 return path.replace(/^\/|\/$/g, ''); } // 获取Bundle内的资源路径 getResourcePath(bundle: string, path: string): string { if (bundle === 'resources') { return this.normalizePath(path); } return `${bundle}/${this.normalizePath(path)}`; } // 检查路径有效性 validateResourcePath(path: string): boolean { // 不允许特殊字符 if (/[\*\?\"\<\>\|]/.test(path)) { return false; } // 不允许相对路径 if (path.includes('..')) { return false; } return true; } } ``` **章节来源** - [ResLoader.ts](file://extensions/oops-plugin-framework/assets/core/common/loader/ResLoader.ts#L240-L335) ## 最佳实践指南 ### 资源加载最佳实践 #### 1. 分阶段加载策略 ```typescript class StagedLoader { private readonly STAGES = [ { name: 'core', priority: 1, bundles: ['resources', 'common'] }, { name: 'ui', priority: 2, bundles: ['ui', 'fonts'] }, { name: 'game', priority: 3, bundles: ['game', 'heroes'] }, { name: 'audio', priority: 4, bundles: ['audio'] } ]; async loadGameResources() { for (const stage of this.STAGES) { console.log(`开始加载阶段: ${stage.name}`); const promises = stage.bundles.map(bundle => oops.res.loadBundle(bundle) ); await Promise.all(promises); console.log(`阶段 ${stage.name} 加载完成`); // 可选:添加视觉反馈 this.updateLoadingScreen(stage.name); } } } ``` #### 2. 资源预加载策略 ```typescript class Preloader { private preloadedResources: Map = new Map(); // 预加载常用资源 async preloadCommonResources() { const commonPaths = [ 'ui/button', 'ui/background', 'common/icons', 'common/fonts' ]; await Promise.all(commonPaths.map(path => this.preloadResource(path) )); } // 预加载单个资源 private async preloadResource(path: string) { try { const asset = await oops.res.loadAsync(path, Asset); this.preloadedResources.set(path, asset); } catch (error) { console.warn(`预加载失败: ${path}`, error); } } // 获取预加载资源 getResource(path: string): Asset | null { return this.preloadedResources.get(path) || null; } } ``` #### 3. 错误处理和降级策略 ```typescript class RobustLoader { private readonly RETRY_COUNT = 3; private readonly TIMEOUT = 10000; async loadResourceWithFallback(path: string, type: AssetType) { let lastError: Error; for (let i = 0; i < this.RETRY_COUNT; i++) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.TIMEOUT); const asset = await oops.res.loadAsync(path, type); clearTimeout(timeoutId); return asset; } catch (error) { lastError = error as Error; console.warn(`资源加载失败(${i + 1}/${this.RETRY_COUNT}): ${path}`, error); if (i < this.RETRY_COUNT - 1) { await this.delay(1000 * (i + 1)); } } } // 降级处理 return this.provideFallbackResource(path, type); } private provideFallbackResource(path: string, type: AssetType): Asset { // 提供默认资源 console.warn(`使用降级资源: ${path}`); return this.createDefaultAsset(type); } private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } } ``` ### 性能优化最佳实践 #### 1. 资源压缩和优化 ```typescript class ResourceOptimizer { // 根据设备性能调整资源质量 getOptimizedResourcePath(basePath: string, deviceInfo: DeviceInfo): string { const qualitySuffix = this.getQualitySuffix(deviceInfo); return `${basePath}_${qualitySuffix}`; } private getQualitySuffix(deviceInfo: DeviceInfo): string { if (deviceInfo.gpuPerformance === 'high') { return 'hd'; } else if (deviceInfo.gpuPerformance === 'medium') { return 'sd'; } else { return 'ld'; } } // 动态调整资源分辨率 async loadOptimizedImage(path: string, targetWidth: number): Promise { const original = await oops.res.loadAsync(path, ImageAsset); // 根据目标宽度调整图片 const resized = this.resizeImage(original, targetWidth); return this.createSpriteFrame(resized); } } ``` #### 2. 缓存策略优化 ```typescript class CacheManager { private readonly CACHE_CONFIG = { maxItems: 100, maxSize: 1024 * 1024 * 50, // 50MB ttl: 300000, // 5分钟 evictionPolicy: 'lru' as 'lru' | 'lfu' }; private cache: Map = new Map(); // 智能缓存策略 getCachedOrLoad(path: string, type: AssetType): T | null { const cached = this.cache.get(path); if (cached && !this.isExpired(cached)) { // 更新访问时间 cached.lastAccess = Date.now(); return cached.asset as T; } // 加载新资源 const asset = oops.res.get(path, type); if (asset) { this.cache.set(path, { asset, lastAccess: Date.now(), size: this.estimateAssetSize(asset) }); this.enforceCacheLimits(); } return asset; } private enforceCacheLimits() { if (this.cache.size > this.CACHE_CONFIG.maxItems) { this.evictLeastUsed(); } } private evictLeastUsed() { const sorted = Array.from(this.cache.entries()) .sort((a, b) => a[1].lastAccess - b[1].lastAccess); const itemsToEvict = Math.floor(sorted.length * 0.2); // 清理20% for (let i = 0; i < itemsToEvict; i++) { this.cache.delete(sorted[i][0]); } } } ``` ### 开发调试最佳实践 #### 1. 资源加载日志 ```typescript class ResourceLogger { private static instance: ResourceLogger; private constructor() {} static getInstance(): ResourceLogger { if (!ResourceLogger.instance) { ResourceLogger.instance = new ResourceLogger(); } return ResourceLogger.instance; } logLoadStart(path: string, type: string) { console.group(`🔍 加载资源: ${path}`); console.log(`类型: ${type}`); console.time(`加载时间`); } logLoadEnd(path: string, success: boolean, duration: number) { console.timeEnd(`加载时间`); console.log(`结果: ${success ? '✅ 成功' : '❌ 失败'}`); console.log(`耗时: ${duration.toFixed(2)}ms`); console.groupEnd(); } logMemoryUsage() { const memory = performance.memory; if (memory) { console.log(`内存使用情况:`); console.log(`- 已使用: ${(memory.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`); console.log(`- 总可用: ${(memory.totalJSHeapSize / 1024 / 1024).toFixed(2)} MB`); console.log(`- 限制: ${(memory.jsHeapSizeLimit / 1024 / 1024).toFixed(2)} MB`); } } } ``` #### 2. 资源完整性检查 ```typescript class IntegrityChecker { // 检查资源完整性 async checkResourceIntegrity(path: string, expectedHash: string): Promise { try { const asset = await oops.res.loadAsync(path, Asset); const actualHash = this.calculateHash(asset); const isValid = actualHash === expectedHash; if (!isValid) { console.error(`资源完整性检查失败: ${path}`); console.log(`期望哈希: ${expectedHash}`); console.log(`实际哈希: ${actualHash}`); } return isValid; } catch (error) { console.error(`资源完整性检查失败: ${path}`, error); return false; } } private calculateHash(asset: Asset): string { // 实现哈希计算逻辑 return ''; } // 批量检查 async checkMultipleResources(checks: ResourceCheck[]): Promise { const results = await Promise.all(checks.map(check => this.checkResourceIntegrity(check.path, check.hash) )); return { passed: results.filter(result => result).length, failed: results.filter(result => !result).length, total: checks.length }; } } ``` 通过遵循这些最佳实践,开发者可以构建一个高效、稳定、易于维护的资源管理系统,确保游戏在各种环境下都能提供优秀的用户体验。