# 基于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
};
}
}
```
通过遵循这些最佳实践,开发者可以构建一个高效、稳定、易于维护的资源管理系统,确保游戏在各种环境下都能提供优秀的用户体验。