還沒試過,這一兩天下班來搞一下
新增拦截loadBundle
这样可以动态加载zip
const loadBundle = assetManager.loadBundle.bind(assetManager);
assetManager.loadBundle = function (nameOrUrl: string, ...args) {
const zipBundle = window["zipBundle"] || [];
if (zipBundle.indexOf(nameOrUrl) > -1) {
ZipLoader.ins.loadZip(`${window["__remoteUrl__"]}remote/${nameOrUrl}`)
.then(() => {
loadBundle(nameOrUrl, ...args);
});
} else {
loadBundle(nameOrUrl,...args);
}
};
我按你的改了以后,发现.js不能被加载,因为downscript不是直接走的http,或者你那里有什么好的方案,可以告知一下吗
这个模型加载真的是头疼的问题,感谢大佬分享。
大佬请教一下,我是用3.8.4打开demo工程的,但是assetsUrlRecordList.json文件里面一直没有内容的,也没有看到F12里面有什么截图里面的资源数组的log出现,这个是怎么回事啊?
而且还有个问题:debug.ts:79 [ZipLoader] The requested asset not in cache: /query-extname/b59e0761-ea04-4a78-b7c0-1672219c7f18
这个是其他插件的query-extname,也被tsc监控了很奇怪,我明明是在webzip文件夹里面运行的tsc啊
這個真的是比較複雜的一步:
- 在 Assets 中找到 web-zip-bundle 資料夾
- 在資料夾中找到 zip-loader-boot 場景
- 場景中會有 ZipLoader 組件,在 Load Next Scen 欄位填入你的項目原本啟動的場景名稱。
- 在 Build Setting 中設定 zip-loader-boot 為第一個載入場景 (Start Scene)。
- 建置並執行
- 瀏覽器執行中按下 F12 開啟 Develop Console
- 接著 ALT + W (Mac: option + W)
- Console 會跑出
assetsUrlRecordList.json的內容 - 複製貼回
assetsUrlRecordList.json檔案中 - 再重新建置一次專案即可
第7步没有这个热键功能的吧?你在代码里面做了按键监听了吗?
没有assetsUrlRecordList.json的内容是不是这个就没有优化效果了啊?我测试的两个方法不管怎么设置都是一样request数量(本地浏览器调试)。
需要勾选调试模式
感謝幫忙解答,我自己一時間都忘記這個設定要勾,ALT + W (Mac: option + W) 才會生效。

是的,專案建置時會依照這個List的內容,將資源打包入zip。
然后 在 3.74 上面
这里要改成
不然请求 都失败了 我不知道 是不是 我别的地方有改动
还是没看明白怎么用,连怎么验证到底有没有生效的方法都没懂啊。
import { assetManager, log} from “cc”;
import * as zip from “jszip”;
const JSZip = zip[“default”];
export class ZipManager {
private static _instance: ZipManager;
private _caches: Map<string, JSZip> = new Map();
private _fileCaches: Map<string, any> = new Map();
private _cachesKey = ""
public static get instance(): ZipManager {
if (!this._instance) {
this._instance = new ZipManager();
}
return this._instance;
}
private _webZipDownloader(url: string, options: any, onComplete: Function) {
const xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open('GET', url, true);
if (options.onProgress) {
xhr.onprogress = (e) => {
if (e.lengthComputable) {
options.onProgress(e.loaded / e.total);
}
};
}
xhr.onload = () => {
if (xhr.status === 200 || xhr.status === 0) {
onComplete(null, xhr.response);
} else {
onComplete(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
};
xhr.onerror = () => {
onComplete(new Error('Download failed'));
};
xhr.send();
}
private _webZipParser(zipData: ArrayBuffer, options: any, onComplete: Function) {
JSZip.loadAsync(zipData)
.then(zip => onComplete(null, zip))
.catch(err => onComplete(err));
}
constructor(){
assetManager.downloader.maxConcurrency = 6;
assetManager.downloader.maxRequestsPerFrame = 3;
// 2. 注册自定义扩展名
assetManager.downloader.register('.zip', this._webZipDownloader);
assetManager.parser.register('.zip', this._webZipParser);
}
initZip(){
const originalDownload = assetManager.downloader.download;
assetManager.downloader["oldDownload"] = originalDownload
let _zip = this._caches.get(this._cachesKey)
assetManager.downloader.download = (id: string, url: string, type: string, options: Record<string, any>, onComplete: ((err: Error | null, data?: any) => void)): void => {
if( type == ".mp3"
||type == ".ccon" )
{
assetManager.downloader["oldDownload"](id,url,type, options, onComplete);
}
else
{
ZipManager.instance.getFileFromZip(_zip,url,type,(err,data)=>{
if(err != null){
assetManager.downloader["oldDownload"](id,url,type, options, onComplete);
}
else{
if(type == ".cconb"){
let ccon = cc.internal.decodeCCONBinary(new Uint8Array(data))
onComplete(null,ccon);
}
else{
onComplete(err,data);
}
}
});
}
};
const oldOpen = XMLHttpRequest.prototype.open
XMLHttpRequest.prototype.open = function(method,url){
if(ZipManager.instance.hasKey(url as string)){
this['zipCacheUrl'] = url
}
return oldOpen.apply(this,arguments)
}
const oldSend = XMLHttpRequest.prototype.send
XMLHttpRequest.prototype.send = function(data){
let _url = this['zipCacheUrl']
if(_url != null && _url.length > 0){
if(ZipManager.instance._fileCaches.has(_url) == false){
ZipManager.instance.getFileFromZip(_zip,_url,'.'+this.responseType,()=>{
this.onload()
})
}
return
}
else{
return oldSend.apply(this,arguments)
}
}
const accessor = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype,'response');
Object.defineProperty(XMLHttpRequest.prototype,'response',{
set:function(str){
},
get:function(){
if(this['zipCacheUrl']){
let data = ZipManager.instance._fileCaches.get(this['zipCacheUrl'])
return data
}
return accessor.get.call(this)
},
configurable:true
})
}
/**
* 加载ZIP文件
* @param url ZIP文件URL
* @param progressCallback 进度回调
* @param options 选项
*/
public async loadBundleZip(
url: string,
progressCallback?: (progress: number) => void,
options?: { cacheKey?: string },
compileCallback?:()=>void)
{
const cacheKey = options?.cacheKey || url;
this._cachesKey = cacheKey
log("cacheKey : ",cacheKey)
// 检查缓存
if (this._caches.has(cacheKey)) {
return this._caches.get(cacheKey)!;
}
// 发起请求
this._fetchWithProgress(
url,
progressCallback,
async (arrayBuffer)=>{
const zip = await JSZip.loadAsync(arrayBuffer);
// 缓存结果
this._caches.set(cacheKey, zip);
compileCallback();
}
);
}
hasKey(url){
let _zip = this._caches.get(this._cachesKey)
if(_zip.file(url)){
return true
}
return false
}
public async getFileFromZip(
zip: JSZip,
path: string,
type: string,
onComplete?,
cache: boolean = true
){
log("getFileFromZip type :",type)
// 检查文件缓存
const cacheKey = path;// `${zip.name || ''}_${path}`;
if (cache && this._fileCaches.has(cacheKey)) {
onComplete(null,this._fileCaches.get(cacheKey))
return
}
const file = zip.file(path);
if (!file) {
onComplete("is not file : "+ path,null);
return
}
type = type.toLowerCase()
let result = null
if(path.includes(".png") == true){
result = await file.async('blob');
}
else if(path.includes(".jpg") == true){
result = await file.async('blob');
}
else if(path.includes(".jpeg") == true){
result = await file.async('blob');
}
else if(path.includes(".webp") == true){
result = await file.async('blob');
}
else{
switch (type) {
case '.png':
case '.jpg':
case '.jpeg':
case '.webp':
case '.mp3':
// 图片返回ArrayBuffer
result = await file.async('blob');
break;
case '.binary':
case '.bin':
case '.dbbin':
case '.skel':
case '.cconb':
case '.arraybuffer':
// JSON文件返回文本
result = await file.async('arraybuffer');
break;
case '.json':
case '.ExportJson':
case '.ccon':
result = await file.async('text');
result = JSON.parse(result)
break;
default:
// 其他文件返回Uint8Array
result = await file.async('text');
break;
}
}
// // 获取文件内容
// let content = file.async(type);;
// // 缓存文件内容
if (cache) {
this._fileCaches.set(cacheKey, result);
}
if(onComplete){
onComplete(null,result)
}
//return result;
}
/**
* 释放指定ZIP缓存
* @param cacheKey 缓存键
*/
public releaseZip(cacheKey: string): void {
if (this._caches.has(cacheKey)) {
this._caches.delete(cacheKey);
// 清理相关文件缓存
Array.from(this._fileCaches.keys())
.filter(key => key.startsWith(`${cacheKey}_`))
.forEach(key => this._fileCaches.delete(key));
}
}
/**
* 释放所有缓存
*/
public clearAllCache(): void {
this._caches.clear();
this._fileCaches.clear();
}
// 私有方法 -------------------------------
private async _fetchWithProgress(
url: string,
progressCallback?: (progress: number) => void,
finishCallback?:(data)=>void
){
console.log("_fetchWithProgress 1 start ")
fetch(url, { mode: 'cors' }) // 注意:通常不需要在同源请求中设置 mode: 'cors'
.then(response => {
console.log("_fetchWithProgress 2 start ")
const contentLength = +response.headers.get('Content-Length'); // 获取内容长度
let receivedLength = 0; // 接收到的长度
const reader = response.body.getReader();
const content = new Uint8Array(contentLength); // 根据内容长度创建缓冲区
let position = 0; // 当前填充位置
function read() {
return reader.read().then(({done, value}) => {
if (done) {
console.log('读取完成');
finishCallback(content)
return;
}
content.set(value, position); // 将数据填充到缓冲区中
position += value.length; // 更新位置
receivedLength += value.length; // 更新接收到的长度
console.log(`已接收:${receivedLength} / ${contentLength}`); // 打印进度
progressCallback(receivedLength/contentLength)
return read(); // 递归读取下一个数据块
});
}
read(); // 开始读取
})
.catch(error => console.error('请求失败', error));
}
}
let _url = “assets/game.zip”
ZipManager.instance.loadBundleZip(_url,(pro)=>{
//下载进度展示
},{ cacheKey: name },()=>{
ZipManager.instance.initZip()
//资源加载
})
不展示zip加载进度,可用使用 cc 的方式下载zip
这里只用了单zip,需要多zip的可用做扩展
同样,现在web上遇到了http请求好几千,希望新用户进入减少http请求加速下载; 这是目前可行的最优方案了吧。。单单是压缩json我就觉得提升很大
web环境bundle打包,官方浅做一下就能实现啊。。
web上我亲测没问题,微信小游戏“Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, ‘response’)” response是空的,不知道怎么搞了。
微信小游戏就不用这个方案啦,这个针对web的,微信小游戏就用官方的分包和zip就好啦
嗯嗯,我已经用官网分包方案了
我发现把bundle稍微细分一下,然后打包选择"合并所有json",请求数就会变成一个bundle一个,进游戏前预加载bundle也能达到减少请求的目的。配合楼主这个方案,在web端把比较零散的资源合并起来,开启一个界面请求数可以达到个位数。合并后的json会有几M,CDN开gzip压缩会小很多,进游戏速度也能接受