使用Gulp构建Egret微信小游戏

最近在做手机游戏往微信小游戏移植。我们试图将一个在iOS上能够消耗600M内存、场景中超过10W个gameobject的Unity游戏移植到只允许4M代码包大小、50M资源缓存的微信小游戏平台上。由于原先的项目资源管理没有按照程序制定的标准,也没有TA的存在,这真是个异常艰巨的任务。

目前我们第一步已经将初期60M的资源压缩到了26M还可以继续压缩,似乎所有资源在50M以内似乎比较乐观的。

配合资源压缩的gulp自动构建已经初步搭起来了,下面是一些开发的记录。

项目需求

Egret有两种方式可以导出微信小游戏工程。一种是在EgretLauncher中手动导出。另外一种是使用cli命令egret publishegret run进行编译导出。但是使用命令行工具在导出有bug,不会自动同步EgretWeixinSupport中的文件到导出工程,会使得命令执行失败。同时由于微信小游戏的代码包大小限制,游戏资源必须上传到服务器。这样就需要在构建流程中添加对资源处理的过程。使用EgretLauncher进行导出就无法满足需求。

对于使用命令行构建来说,Egret项目支持扩展cli命令,相关文档在命令行扩展插件。由于Egret项目结构不是标准的npm项目,也就不能直接使用node.js的大量工具和库,所以通过Egret中的scrip进行扩展限制较大。

最后是单独实现了一个基于gulp的项目自动构建,具有如下的Feature:

  • 可以通过命令行导出,使用本地资源的版本以及使用服务器资源的版本
  • 支持自动将Resource目录下的资源上传到AliyunOSS(可以使用gulp插件实现其他Server解决方案)
  • 支持使用TinyPNG对图片资源进行压缩
  • 资源处理缓存支持,优化多次构建的时间

Quick Start

环境配置 - 安装Node.js 8.11最新的LTS版本 - 使用npm i -g gulp 安装gulp - 安装微信开发者工具 - 安装白鹭引擎微信导出库

目录结构说明

由于egret项目在打包时会将项目目录整体添加到tsc的include中,所以不能在改目录下添加gulpfile.js或者是package.json文件。 会导致egret build 命令行出错。所以将gulpfile.js放在项目工程的上级目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Mode                LastWriteTime         Length Name
---- ------------- ------ ----
d----- 2018/6/8 15:36 Cache ///自动构建的缓存文件目录
d----- 2018/6/8 18:09 node_modules
d----- 2018/6/8 15:35 TestMap //Egret 工程目录
d----- 2018/6/11 14:16 TestMap_res_953fb879118301 //Egret 工程导出的资源目录
d----- 2018/6/11 14:16 TestMap_wxgame //Egret 工程导出的微信小游戏工程
-a---- 2018/6/8 15:12 91 .gitignore
-a---- 2018/6/12 12:35 858 config.json //自动构建读取的配置文件
-a---- 2018/6/7 11:38 0 egret_publish.bat
-a---- 2018/6/7 11:37 8 egret_run.bat
-a---- 2018/6/7 11:52 15 egret_run_remote.bat
-a---- 2018/6/12 12:35 9906 gulpfile.js //gulp脚本
-a---- 2018/6/8 18:09 774 package.json

gulp task

gulpfile.js中定义了多个task,使用gulpSeq实现多任务的顺序执行。

主要的两个task为 run_remoterun

run_remote会使用egret build导出微信小游戏工程,然后将resource目录下的资源处理之后上传到aliyunOSS服务器,最后配置egret项目使用OSS上传的资源地址,并拉起微信开发者工具。

而run直接是导出微信小游戏工程后直接处理资源,最后拉起微信开发者工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
gulp.task("run_remote", gulpSeq(
"export_clean",
"wx_copy",
["wx_setid","copy_cisconfig"],
"egret_build",
"res_clean",
[
"res_process_img",
"res_process_json"
],
"wx_resclean",
"oss_sync",
"run_wx"
));

gulp.task("run", gulpSeq(
"export_clean",
"wx_copy",
["wx_setid","copy_cisconfig"],
"egret_build",
"run_wx"
));

在目录下直接执行 gulp rungulp run_remote就可以直接执行。

构建开发说明

在实现自动构建时遇到了一些问题,在下面做一些记录。

配置文件

项目目录创建了一个config.json文件,用于配置gulp的一些参数。

  • projectfolder: 定义了当前egret工程目录
  • wxappid:会在egret导出wx工程后配置
  • projectname: 导出的wx工程的项目名称
  • res/compressFolders:配置所有需要进行图片压缩的目录
  • ossconf:aliyun OSS配置
  • ossconf_cache: 同步OSS时生成的缓存文件路径
  • server_rul:微信资源下载的远程路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"projectfolder": "TestMap",
"wxappid": "wx6ac********",
"projectname":"testproj",
"res":{
"compressFolders": [
"spriteatlas",
"UIAtlas"
]
},
"ossconf":{
"connect":{
"region": "oss-cn-hangzhou",
"accessKeyId": "[oss key]",
"accessKeySecret":"[oss secret]",
"bucket": "[oss buckname]"
},
"controls":{
"headers":{
"Cache-Control": "no-cache"
}
},
"setting":{
"dir":"/",
"noClean": false,
"force": false,
"quiet": true
}
},
"ossconf_cache":{
"cacheFileName":"Cache/oss/"
},
"server_rul": "https://test.com"

}

同步EgertWeixinSupport到导出的微信工程

由于egret publish的命令行执行会报错,不会自动同步微信导出文件。所以我们要手动将微信项目模板文件导出到projname_wxgame目录中。

参考gulp.task('wx_copy')

在windows下路径为<username>\AppData\Roaming\EgretLauncher\download\EgretWeixinSupport\

在macos中~/Library/Application\ Support/EgretLauncher/download/EgretWeixinSupport

通过命令行启动微信开发者工具

微信开发工具支持命令行启动自动打开项目

<微信开发者工具安装目录>/cli -o <projectpath>

所以需要获取到微信开发者工具的安装路径,在macos中可以直接使用:

/Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin/cli

对于window上来说,用户可能会按照到不同的磁盘和目录。微信开发者工具会将目录写入windows的系统注册表,使用reg query HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Tencent\\微信web开发者工具可以获取到微信开发者工具的安装目录。

需要注意的是windows的cmd对于中文是使用gbk编码的而不是utf8,所以需要对调用命令行返回的字符串进行编码的处理。

使用Tiny Png进行图片压缩

微信小游戏对于本地资源只有50m的缓存,也就是说游戏的本地资源文件最多有50M。使用TinyPng最多可以将图片压缩到原始大小的20%。

在搜索了多个gulp-tiny插件之后最后使用gulp-tinypng-nokey进行图片压缩。 由于该插件没有实现图片缓存,每次构建如果都通过网路获取压缩的图片,需要很长的时候。所以手动实现了图片资源的缓存。考虑后续使用gulp-remember等插件进行缓存。

1
2
3
4
5
6
7
8
9
10
gulp.task("res_process_img", () => {
return gulp.src(RES_FILTER.Image)
.pipe(filter((file) => CheckImageCache(file)))
.pipe(tinypng())
.pipe(gulp.dest(ProjectResFolder + '/resource/assets/'))
.pipe(filter(file => {
RestoreImageToCache(file);
return true;
}));
});

Json压缩

由于从Unity引擎中导出的动画数据对于浮点数没有进行精度的压缩,转成json文件后这部分会造成部分空间浪费。

可以通过遍历所有json数据文件对浮点数的精度进行压缩。最后20M的json数据减少了6M的大小。

1
2
3
4
5
6
7
8
9
10
11
function FixNumber(obj,size){
for(let m in obj){
let mt = typeof(obj[m]);
if(mt === 'number'){
obj[m]=Number(obj[m].toFixed(size));
}
else if(mt === 'object'){
FixNumber(obj[m],size);
}
}
}

在gulp中,所有的文件流是基于在内存中的虚拟文件系统vinyl。要对文件流进行操作也就需要编写gulp的插件。

具体可以参考:

深入理解 Node.js Stream 内部机制

探究Gulp的Stream

后续考虑使用Bson对json数据进行压缩,能够更大幅度地减少资源的大小。

Egret使用远程资源与本地资源模式切换

Egret的Res支持从远程读取配置或者是从本地读取资源。我们需要切换不同的资源加载模式,游戏的代码需要根据配置的方式选择相应的加载远,在自动构建的阶段根据调用的命令行进行修改这个配置文件。 然而由于这个配置是控制资源文件的加载所以不能使用Egret的RES进行加载。

所以我们需要使用微信小游戏的资源API先加载这个配置文件,根据配置文件中配置的信息决定Egret引擎加载本地或者是远程的项目资源。

配置文件以及代码如下:

1
2
3
4
5
6
7
{
"res":{
"use_remote": false,
"remote_url": "https://xxxx.xxxgame.cn/resource/",
"remote_json_url":"https://xxxx.xxxgame.cn/resource/default.res.json"
}
}
1
2
3
4
5
6
7
8
if(cisconf.res.use_remote){
egret.log("Load RemoteRes: "+cisconf.res.remote_json_url);
await RES.loadConfig(cisconf.res.remote_json_url,cisconf.res.remote_url);
}
else{
egret.log("Load LocalRes");
await RES.loadConfig("resource/default.res.json", "resource/");
}

GulpFile.js

gulpfile.js可以自行修改使用。

使用Gulp构建Egret微信小游戏 · GitHub