小白也能写框架之【三、带进度的分包管理器】

接上篇:小白也能写框架之【二、带颜色的日志管理器】

我们直接撸分包管理器的代码,我们都知道cocos默认分包加载只有是否完成回调,但是没进度回调
那我们自己撸一个。

一、新建assets\Core\Scripts\Managers\BundleMgr.ts,直接上代码:

import { assetManager, AssetManager } from "cc";

import { logMgr } from "./LogMgr";

/**

 * 分包管理器

 * 提供分包加载、获取、移除功能。

 */

class BundleMgr {

    /** 私有构造函数,确保外部无法直接通过new创建实例 */

    private constructor() {}

    /** 单例实例 */

    public static readonly instance: BundleMgr = new BundleMgr();

    /**

     * 获取指定分包,如果未加载则进行加载。

     * @param nameOrUrl - 分包名称或URL。

     * @param onProgress - 进度回调函数。

     * @returns Promise<AssetManager.Bundle | null> - 加载完成后的Promise。

     */

    public async getBundle(nameOrUrl: string, onProgress?: (progress: number) => void): Promise<AssetManager.Bundle | null> {

        const bundle = assetManager.getBundle(nameOrUrl);

        if (bundle) return bundle;

        try {

            const loadedBundle = await this.loadBundle(nameOrUrl);

            if (onProgress) {

                await this.loadAssetsWithProgress(loadedBundle, onProgress);

            }

            return loadedBundle;

        } catch (error) {

            logMgr.err(`分包 ${nameOrUrl} 加载失败`, error.message);

            return null;

        }

    }

    /**

     * 加载指定分包。

     * @param nameOrUrl - 分包名称或URL。

     * @returns Promise<AssetManager.Bundle> - 加载完成后的Promise。

     */

    private loadBundle(nameOrUrl: string): Promise<AssetManager.Bundle> {

        return new Promise((resolve, reject) => {

            assetManager.loadBundle(nameOrUrl, (err, loadedBundle) => {

                if (err) {

                    reject(err);

                } else {

                    resolve(loadedBundle);

                }

            });

        });

    }

    /**

     * 加载分包中的资源并提供进度反馈。

     * @param bundle - 已加载的分包。

     * @param onProgress - 进度回调函数。

     * @returns Promise<void> - 加载完成后的Promise。

     */

    private loadAssetsWithProgress(bundle: AssetManager.Bundle, onProgress: (progress: number) => void): Promise<void> {

        return new Promise((resolve, reject) => {

            const assets = bundle.getDirWithPath('');

            const totalAssets = assets.length;

            let loadedAssets = 0;

            if (totalAssets === 0) {

                onProgress(1);

                resolve();

                return;

            }

            assets.forEach((asset) => {

                bundle.load(asset.path, (err) => {

                    if (err) {

                        reject(err);

                        return;

                    }

                    loadedAssets++;

                    onProgress(loadedAssets / totalAssets);

                    if (loadedAssets === totalAssets) {

                        resolve();

                    }

                });

            });

        });

    }

}

/** 分包管理器实例 */

export const bundleMgr = BundleMgr.instance;

二、代码解释
这段代码实现了一个分包管理器,使用 TypeScript 编写,主要用于管理和加载游戏或应用程序中的分包资源。以下是对其主要功能和结构的简要介绍:

  1. BundleMgr 类:这是分包管理器类,负责加载和管理分包资源。它具有以下特点:

    • 单例模式:通过私有构造函数和静态实例 instance 实现,确保整个应用程序中只有一个 BundleMgr 实例。
    • 获取分包getBundle 方法用于获取指定的分包。如果分包尚未加载,则会进行加载。该方法返回一个 Promise,在加载完成后返回 AssetManager.Bundlenull
    • 加载分包loadBundle 方法负责实际加载分包,并返回一个 Promise,在加载完成后返回 AssetManager.Bundle
    • 进度反馈loadAssetsWithProgress 方法用于加载分包中的资源,并通过回调函数提供加载进度反馈。它计算已加载资源的比例,并在每次资源加载完成时调用进度回调。
  2. 错误处理:在加载过程中,如果发生错误,会使用 logMgr 记录错误信息,确保问题可以被及时发现和解决。

  3. bundleMgr 导出:通过导出 bundleMgr 实例,其他模块可以直接使用这个单例来管理分包资源。

这个分包管理器的设计使得分包资源的加载更加高效和可控,特别是在需要动态加载大量资源的游戏或应用程序中,提供了良好的用户体验和资源管理能力。

三、继续映射到全局,接上篇文章,我们导出映射到app

四、测试代码
1、我们随便新建一个分包,包含一个空的预制体方便测试,如图:

2、assets\App\Main.ts测试代码:

import { _decorator, Component, Node } from 'cc';

const { ccclass, property } = _decorator;

@ccclass('Main')

export class Main extends Component {

    private testNum: number = 1314;

    private testStr: string = 'cocos';

    // 加载

    onLoad() {

        app.bundle.getBundle('TestBundle', this.testLoad)

        .then((bundle) => {

            if (bundle) {

                app.log.info('分包加载成功');

            }

        });

    }

    // 开始

    start() {

        this.testLog();

    }

    // 日志测试

    testLog() {

        app.log.debug('我是日志1');

        app.log.info('我是日志2');

        app.log.warn('我是日志3');

        app.log.err('我是日志4');

        app.log.debug('打印数字变量:', this.testNum);

        app.log.warn('打印文本变量:', this.testStr);

    }

    // 分包进度

    testLoad(progress: number) {

        app.log.info('分包加载进度', progress);

    }

}

五、测试效果:

下一篇预告:小白也能写框架之【四、带加密的数据管理器】

1赞

楼主高产似那啥 加油 :rofl:

大佬,B站出视频吧,我直接给你 投币 点赞 收藏 加充电

嘿嘿,没到那个级别,我就是记录下自己开发的过程。

期间肯定还有bug,怕误人子弟,等用自己的框架写出自己的游戏了,出点视频那是可以的。

你这个进度是把bundle里面所有资源都加载了啊 :sweat_smile: 一般不会这么做吧

:grin:
忘记说明了,我是大厅+子游戏,单场景+多预制体的框架

每个分包都是独立,任何资源也是互不依赖的,所以加载分包,也同时为了预加载资源

一举两得

免得 加载一个子游戏分包后,在后面实例化的时候还要去加载什么音频啊,精灵啊,

这些初始化很浪费时间,所以加载分包的是同时一次性加载分包的资源

子游戏关闭后,再释放整个分包的全部资源

大厅+子游戏的方式好处就是,永远只有一个空客场景+一个分包会被用到,这样内存毫无压力。

我现在的游戏APP 只需要打开一个空壳场景,一个大厅分包,这2个是常驻的

然后子游戏分包就算有几十个也没事,用的时候才加载,不用就释放

内存占用很小

看见大佬的分享,很希望Cocos能在论坛弄个新手板块,方便新手学习知识总结经验。

我也这么想的,有个独立教程版块、提问版块,是极好的。

不然有些帖子新手根本看不到,太混乱了

支持 :grimacing:

大厅+子游戏 第一反应就是QP,当年好多QP大厅

现在也不少啊,但是现在都换模式了,现在的小游戏也开始出现QP的影子了

过不了多久小游戏也是会被监管的对象,把握现在红利时期多搞搞,后面就没机会了。

QP是啥??

挣钱的项目

象棋和打牌简称qp