├── README.md ├── main.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # Cocos Creator 微信小游戏内置资源打包插件 2 | 3 | ## 注意: 4 | Cocos Creator 从v2.4.0开始支持Asset Bundle,里面已经包含初始场景分包的功能,不需要再使用该插件。 5 | 6 | 插件功能:使用Cocos Creator构建微信小游戏项目时,会自动打包指定必要的内置资源到首包。有效解决启动场景因网络延迟或加载失败而导致的黑屏/卡死问题。 7 | 8 | ## 解决的具体问题: 9 | 1. 在玩家首次启动游戏时,加载启动场景/首屏会从远程加载资源,资源在下载过程会出现短暂的黑屏甚至卡死。 10 | 2. 其他资源加载问题,比如Cocos Creator 2.2.0华为快游戏播放音频时会出现卡顿,把对应的音频文件夹打包到包里可以解决这个问题。 11 | 12 | ## 解决思路: 13 | 把启动场景所依赖的必要资源单独放在包里面(本地),其他资源放在服务器 14 | 15 | ## 使用方法: 16 | 1. 把该插件文件夹拷贝到{项目根目录}/packages下,重启Cocos Creator生效。如果想全局所有项目使用该插件,把插件文件夹拷贝到 {用户名}/.CocosCreator/packages下。 17 | 2. 构建微信小游戏项目,勾选MD5 cache,取消勾选调试模式(正式打包才启用该插件功能) 18 | 3. 构建完成后,会多出一个res_internal文件夹,这个文件夹就是内置的资源。把原本res文件夹资源上传到服务器后,移除res文件夹,最后把res_internal重命名为res。 19 | 20 | ## 温馨提示: 21 | 微信官方限制首包最大为4M,所以启动场景占用的资源要尽量的小。建议使用独立的图集,不要依赖其他与启动场景无关的资源。如果对字体有要求也尽量不要放TTF文件,使用图字代替会更省资源。 22 | 23 | ## 扩展功能,如果你还想打包其他资源参考下面的例子 24 | 打包文件夹 queryAssets('db://assets/resources/sounds/**/*'); 25 | 打包具体文件 queryAssets('db://assets/RewardView.prefab'); 26 | 27 | ## 版本更新日志: 28 | 29 | ### 1.0.1 30 | 1. 修复ttf字体文件拷贝路径错误的问题 31 | 2. 修复asset互相依赖导致插件死循环 32 | 33 | ### 1.0.0 34 | 1. 修复构建mp3等资源只拷贝了配置文件,没有拷贝资源本身的问题 35 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 内置资源打包插件 3 | */ 4 | 'use strict'; 5 | 6 | const Path = require('fire-path'); 7 | const Fs = require('fire-fs'); 8 | 9 | function getMd5ByUuidArray(buildResults, uuid) { // raw资源一个uuid有两个md5,例如mp3资源,这里要使用数组处理 10 | return [buildResults._md5Map[uuid], buildResults._nativeMd5Map[uuid]]; 11 | } 12 | 13 | function getUuidFromPackedAssets(buildResults, uuid) { 14 | for (const key in buildResults._packedAssets) { 15 | if (buildResults._packedAssets.hasOwnProperty(key)) { 16 | const item = buildResults._packedAssets[key]; 17 | for (let index = 0; index < item.length; index++) { 18 | const element = item[index]; 19 | if (uuid === element) { 20 | return key; 21 | } 22 | } 23 | } 24 | } 25 | } 26 | 27 | function makeDir(dir) { 28 | if (!Fs.existsSync(dir)) { 29 | Fs.ensureDirSync(dir); 30 | } 31 | } 32 | 33 | function getFilePathArray(buildResults, resDir, uuid, md5Array) { 34 | let asset = buildResults._buildAssets[uuid]; 35 | let pathArray = []; 36 | for (let index = 0; index < md5Array.length; index++) { 37 | const md5 = md5Array[index]; 38 | if (md5) { 39 | let isRawAsset = md5 == buildResults._nativeMd5Map[uuid]; 40 | let extension = isRawAsset ? asset.nativePath.split('.').pop() : 'json'; 41 | let dir; 42 | if (extension.indexOf('ttf') != -1) { // ttf字体文件路径特殊处理 43 | dir = Path.join(resDir, 'raw-assets', uuid.substr(0, 2), `${uuid}.${md5}`); 44 | extension = asset.nativePath.split('/').pop(); 45 | pathArray.push(Path.join(dir, extension)); 46 | } else { 47 | dir = Path.join(resDir, isRawAsset ? 'raw-assets' : 'import', uuid.substr(0, 2)); 48 | pathArray.push(Path.join(dir, `${uuid}.${md5}.${extension}`)); 49 | } 50 | makeDir(dir); 51 | } 52 | } 53 | return pathArray; 54 | } 55 | 56 | function copyFile(src, dst) { 57 | if (!Fs.existsSync(dst)) { 58 | Fs.copySync(src, dst); 59 | // Editor.log(`copy file ${src} ---> ${dst}`); 60 | } 61 | } 62 | 63 | function onBeforeBuildStart(options, callback) { 64 | if (options.actualPlatform === 'wechatgame') { 65 | Fs.removeSync(Path.join(options.dest, 'res')); 66 | Fs.removeSync(Path.join(options.dest, 'res_internal')); 67 | } 68 | callback(); 69 | } 70 | 71 | function onBeforeBuildFinish(options, callback) { 72 | callback(); 73 | } 74 | 75 | function onBuildFinish(options, callback) { 76 | // Editor.log("options", JSON.stringify(options)); 77 | let buildResults = options.buildResults; 78 | 79 | if (options.actualPlatform === 'wechatgame' && !options.debug && options.md5Cache) { 80 | let uuidSet = new Set(); // 避免互相依赖 81 | 82 | function copyAssetByUuid(uuid) { 83 | let md5Array = getMd5ByUuidArray(buildResults, uuid); 84 | if (md5Array && md5Array.length > 0) { 85 | let srcArray = getFilePathArray(buildResults, Path.join(options.dest, 'res'), uuid, md5Array); 86 | let dstArray = getFilePathArray(buildResults, Path.join(options.dest, 'res_internal'), uuid, md5Array); 87 | for (let index = 0; index < srcArray.length; index++) { 88 | const src = srcArray[index]; 89 | const dst = dstArray[index]; 90 | copyFile(src, dst); 91 | } 92 | } 93 | } 94 | 95 | function copyAssets(uuids) { 96 | for (let i = 0; i < uuids.length; ++i) { 97 | let uuid = uuids[i]; 98 | if (uuidSet.has(uuid)) continue; 99 | 100 | let asset = buildResults._buildAssets[uuid]; 101 | if (asset && buildResults.getAssetType(uuid) != 'folder') { 102 | 103 | copyAssetByUuid(uuid); 104 | uuidSet.add(uuid); 105 | 106 | // 依赖数据 107 | let asset = buildResults._buildAssets[uuid]; 108 | asset && asset.dependUuids && copyAssets(asset.dependUuids); // 递归依赖 109 | 110 | // 合并数据 111 | let packedUuid = getUuidFromPackedAssets(buildResults, uuid); 112 | packedUuid && copyAssetByUuid(packedUuid); 113 | } 114 | } 115 | } 116 | 117 | function queryAssets(dbPath) { 118 | Editor.assetdb.queryAssets(dbPath, null, (err, assetInfos) => { 119 | if (!err) { 120 | let array = assetInfos.map(x => x.uuid); 121 | copyAssets(array); 122 | } 123 | }); 124 | } 125 | // 打包启动场景资源 126 | 127 | // 方法1:读路径 128 | // queryAssets('db://assets/Scene/LaunchScene.fire'); 129 | 130 | // 方法2:读配置 131 | var startSceneUuid = options.startScene; 132 | copyAssets([startSceneUuid]); 133 | 134 | } 135 | callback(); 136 | } 137 | 138 | module.exports = { 139 | load() { 140 | Editor.Builder.on('build-start', onBeforeBuildStart); 141 | Editor.Builder.on('before-change-files', onBeforeBuildFinish); 142 | Editor.Builder.on('build-finished', onBuildFinish); 143 | }, 144 | 145 | unload() { 146 | Editor.Builder.removeListener('build-start', onBeforeBuildStart); 147 | Editor.Builder.removeListener('before-change-files', onBeforeBuildFinish); 148 | Editor.Builder.removeListener('build-finished', onBuildFinish); 149 | }, 150 | 151 | messages: { 152 | 'build'() { 153 | Editor.log('内置资源打包插件'); 154 | } 155 | }, 156 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "internal_res_packer", 3 | "version": "1.0.1", 4 | "description": "内置资源打包插件", 5 | "author": "Zhong Yu", 6 | "main": "main.js", 7 | "main-menu": {} 8 | } --------------------------------------------------------------------------------