Files
pixelheros/.qoder/repowiki/zh/content/核心系统/资源加载系统.md
panw 4235e3b776 refactor(game): 移除已弃用的事件常量
- 删除 UpdateHero 和 UpdateFightHero 事件
- 移除 MISSION_UPDATE 事件常量
- 优化游戏事件枚举定义
2025-10-28 16:15:47 +08:00

44 KiB
Raw Blame History

基于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详解
  4. 资源加载场景
  5. Bundle管理策略
  6. 性能优化技术
  7. 内存管理机制
  8. 调试与监控
  9. 常见问题与解决方案
  10. 最佳实践指南

简介

Oops Plugin Framework的资源管理系统是一个功能强大的资源加载与管理框架专为Cocos Creator游戏开发而设计。该系统提供了完整的资源生命周期管理包括加载、缓存、释放和监控等功能支持Spine骨骼动画、SpriteFrame、配置文件等多种资源类型的高效管理。

核心特性

  • 多格式支持支持Spine骨骼动画、SpriteFrame、音频、纹理等多种资源类型
  • 智能缓存:自动管理资源缓存,避免重复加载
  • 异步加载:支持进度回调和错误处理
  • Bundle管理:灵活的资源包划分和热更新支持
  • 内存优化:自动垃圾回收和手动释放机制
  • 调试工具:内置资源状态监控和调试功能

系统架构

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

图表来源

章节来源

核心API详解

load - 单资源加载

load方法是最基础的资源加载API支持多种重载形式以适应不同的使用场景。

基本语法

// 加载默认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<T> 资源类型构造函数
onProgress ProgressCallback 加载进度回调函数
onComplete CompleteCallback<T> 加载完成回调函数

使用示例

加载Spine骨骼动画

// 加载角色动画资源
oops.res.load("hero/skeleton", sp.SkeletonData, (err, skeletonData) => {
    if (err) {
        console.error("角色动画加载失败:", err);
        return;
    }
    this.skeletonData = skeletonData;
    this.updateSkeleton();
});

加载SpriteFrame

// 加载UI按钮图片
oops.res.load("ui/button_normal", SpriteFrame, (err, spriteFrame) => {
    if (err) {
        console.error("按钮图片加载失败:", err);
        return;
    }
    this.button.spriteFrame = spriteFrame;
});

loadDir - 文件夹批量加载

loadDir方法用于批量加载指定目录下的所有资源,特别适合加载同一类型的资源集合。

语法结构

oops.res.loadDir(bundleName, dir, type, onProgress, onComplete);

进度回调机制

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[])

图表来源

实际应用案例

游戏资源加载流程:

// 加载游戏主界面资源
oops.res.loadDir("game", (finished, total, item) => {
    // 更新加载进度
    this.updateProgressBar(finished, total);
    console.log(`正在加载: ${item.uuid} (${finished}/${total})`);
}, () => {
    // 所有资源加载完成
    this.onGameResourcesLoaded();
});

章节来源

loadRemote - 远程资源加载

loadRemote方法专门用于加载网络上的远程资源,支持图片、音频等文件的动态下载。

方法签名

oops.res.loadRemote<T extends Asset>(url: string, options: IRemoteOptions, onComplete: CompleteCallback<T>);

配置选项

选项 类型 描述
ext string 文件扩展名,如".png"、".mp3"
timeout number 请求超时时间(毫秒)
headers Record<string, string> 自定义请求头

完整实现示例

// 加载远程图片资源
const options: IRemoteOptions = {
    ext: ".png",
    timeout: 10000
};

oops.res.loadRemote<ImageAsset>(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方法支持从服务器动态加载资源包,实现游戏的热更新功能。

使用流程

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

图表来源

热更新实现

// 热更新资源包
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();
    }
}

章节来源

release & releaseDir - 资源释放

资源释放是内存管理的关键环节,系统提供了精确的资源释放机制。

release - 单资源释放

// 释放单个资源
oops.res.release("model/player", "resources");

// 释放其他Bundle中的资源
oops.res.release("ui/button", "assets");

releaseDir - 批量资源释放

// 释放整个目录的资源
oops.res.releaseDir("game", "resources");

// 释放后清理Bundle如果需要
oops.res.releaseDir("temp", "temporary");

内存管理原理

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

图表来源

章节来源

资源加载场景

Spine骨骼动画加载

Spine骨骼动画是游戏中常见的动画表现形式系统提供了专门的加载和管理机制。

加载流程

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组件实现

// 在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动画控制器

// 动画播放控制
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';
}

章节来源

SpriteFrame精灵图加载

SpriteFrame是UI和游戏元素的基础资源类型系统提供了高效的加载和管理机制。

加载策略

场景 推荐策略 示例
UI按钮 同步加载 oops.res.load("ui/button", SpriteFrame)
背景图片 异步加载 oops.res.loadDir("background", ...)
角色皮肤 延迟加载 游戏开始后按需加载
动态图标 缓存复用 首次加载后缓存

实际应用

// UI按钮状态管理
class ButtonStateManager {
    private buttonStates: Map<string, SpriteFrame> = 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;
        }
    }
}

配置文件加载

配置文件是游戏运行时的重要数据源,系统提供了灵活的配置加载机制。

配置文件组织结构

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]

图表来源

加载实现

// 配置文件加载器
class ConfigLoader {
    private configs: Map<string, any> = 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<any> {
        return new Promise((resolve, reject) => {
            oops.res.load(path, JsonAsset, (err, jsonAsset) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve(JSON.parse(jsonAsset.json));
            });
        });
    }
}

章节来源

Bundle管理策略

Bundle划分原则

合理的Bundle划分是资源管理的基础遵循以下原则

1. 功能模块划分

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. 加载优先级划分

// 高优先级资源(立即加载)
const highPriorityBundles = [
    "resources",      // 核心引擎资源
    "common",         // 公共组件资源
    "ui/loading"      // 加载界面资源
];

// 中优先级资源(预加载)
const mediumPriorityBundles = [
    "game",           // 游戏主资源
    "language"        // 语言包资源
];

// 低优先级资源(延迟加载)
const lowPriorityBundles = [
    "heroes",         // 英雄资源
    "skills"          // 技能资源
];

Bundle配置管理

构建配置

builder.json中配置Bundle压缩和分发策略

{
  "bundleConfig": {
    "custom": {
      "auto_98AwgBoURL0KsT0uXAsTE4": {
        "displayName": "resources",
        "configs": {
          "native": {
            "preferredOptions": {
              "compressionType": "merge_dep",
              "isRemote": false
            }
          },
          "wechatgame": {
            "compressionType": "subpackage",
            "isRemote": false
          }
        }
      }
    }
  }
}

运行时Bundle管理

class BundleManager {
    private loadedBundles: Set<string> = 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);
    }
}

章节来源

性能优化技术

批量加载优化

并行加载策略

// 使用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);
    }
}

分批加载策略

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<T>(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<void> {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

延迟加载策略

按需加载实现

class LazyResourceManager {
    private loadedResources: Map<string, Asset> = new Map();
    private loadingPromises: Map<string, Promise<Asset>> = new Map();
    
    // 按需加载资源
    async getResource<T extends Asset>(path: string, type: AssetType<T>): Promise<T> {
        // 检查缓存
        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<T>((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);
            }
        });
    }
}

内存预分配优化

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垃圾回收器处理
    }
}

章节来源

内存管理机制

引用计数系统

系统采用自动引用计数机制管理资源生命周期。

引用计数原理

stateDiagram-v2
[*] --> Created : 创建资源
Created --> Referenced : addRef()
Referenced --> Referenced : addRef()
Referenced --> Released : decRef()
Released --> [*] : refCount === 0
Referenced --> Destroyed : destroy()
Destroyed --> [*] : 清理资源

自动释放机制

class ResourceManager {
    private assets: Map<string, Asset> = new Map();
    private refCounts: Map<string, number> = new Map();
    
    // 加载资源并增加引用计数
    loadWithRefCount<T extends Asset>(path: string, type: AssetType<T>): 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();
    }
}

垃圾回收策略

自动垃圾回收

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);
            });
        }
    }
}

手动释放策略

场景化释放

class SceneResourceManager {
    private sceneResources: Set<string> = 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);
        });
    }
}

章节来源

调试与监控

资源状态监控

系统提供了完整的资源状态监控和调试功能。

dump方法使用

// 打印所有缓存资源信息
oops.res.dump();

// 输出示例:
// Asset {uuid: "123456", nativeUrl: "assets/resources/ui/button.png"}
// Asset {uuid: "789012", nativeUrl: "assets/resources/audio/bg_music.mp3"}
// 当前资源总数:156

资源统计工具

class ResourceMonitor {
    private statistics: Map<string, ResourceStats> = 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 }));
    }
}

性能监控

加载性能分析

class PerformanceMonitor {
    private loadTimes: Map<string, number[]> = 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`);
            }
        });
    }
}

章节来源

常见问题与解决方案

资源未释放导致内存溢出

问题描述

资源加载后没有及时释放,导致内存持续增长,最终引发内存溢出。

解决方案

// 错误示例:忘记释放资源
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<string> = 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();
    }
}

自动化资源管理

class AutoResourceManager {
    private resourceTracker: Map<string, {refCount: number, lastAccess: number}> = 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);
            }
        });
    }
}

异步加载顺序错乱

问题描述

多个异步加载任务同时进行,导致资源加载完成后处理顺序混乱。

解决方案

// 错误示例:异步顺序错乱
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<any>, callback: CompleteCallback<any>) {
        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加载失败

问题诊断

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<void> {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

资源路径管理问题

路径规范化

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;
    }
}

章节来源

最佳实践指南

资源加载最佳实践

1. 分阶段加载策略

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. 资源预加载策略

class Preloader {
    private preloadedResources: Map<string, Asset> = 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. 错误处理和降级策略

class RobustLoader {
    private readonly RETRY_COUNT = 3;
    private readonly TIMEOUT = 10000;
    
    async loadResourceWithFallback(path: string, type: AssetType<any>) {
        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<any>): Asset {
        // 提供默认资源
        console.warn(`使用降级资源: ${path}`);
        return this.createDefaultAsset(type);
    }
    
    private delay(ms: number): Promise<void> {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

性能优化最佳实践

1. 资源压缩和优化

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<SpriteFrame> {
        const original = await oops.res.loadAsync(path, ImageAsset);
        
        // 根据目标宽度调整图片
        const resized = this.resizeImage(original, targetWidth);
        return this.createSpriteFrame(resized);
    }
}

2. 缓存策略优化

class CacheManager {
    private readonly CACHE_CONFIG = {
        maxItems: 100,
        maxSize: 1024 * 1024 * 50, // 50MB
        ttl: 300000, // 5分钟
        evictionPolicy: 'lru' as 'lru' | 'lfu'
    };
    
    private cache: Map<string, CachedItem> = new Map();
    
    // 智能缓存策略
    getCachedOrLoad<T extends Asset>(path: string, type: AssetType<T>): 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. 资源加载日志

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. 资源完整性检查

class IntegrityChecker {
    // 检查资源完整性
    async checkResourceIntegrity(path: string, expectedHash: string): Promise<boolean> {
        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<ResourceValidationResult> {
        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
        };
    }
}

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