cocos微信小游戏启动首屏优化
[图片]
一、基本信息
引擎版本cocos creator 2.3.3
二、参考文章
1、最佳实践: https://developers.weixin.qq.com/minigame/dev/guide/performance/perf-action-start2.html
2、Cocos Creator 微信小游戏平台启动与包体优化(首屏渲染耗时降低 50%): https://forum.cocos.org/t/cocos-creator-50/94999
三、优化结果对比
3.1 优化前加载速度: 8605ms
[图片]
3.2优化后加载速度:4366ms
[图片]
四、优化前资料准备
根据微信官方文档启动优化最佳实践图示:
[图片]
优化启动速度主要有6点:
1.精简包内资源: 本项目已优化过
2.分包加载: 可以尝试,具体步骤见5.2
3.引擎插件能力: 可以尝试,打包时选择允许引擎分离即
4.预下载能力: 仍处于内测中
5.降低首屏渲染所需资源数目: 可以尝试,具体步骤见5.1
6.尽快渲染: 可以尝试,具体步骤见5.3
五、优化实践
有了思路之后根据复杂度由简入繁按照如下步骤开始优化
5.1 降低首屏渲染所需资源数目
启动页只放一张背景图,除了跳转至加载页不做其他处理,减少启动页的资源加载和逻辑处理
[代码]cc.Class({
extends: cc.Component,
properties: {
},
// LIFE-CYCLE CALLBACKS:
// onLoad () {},
start () {
cc.director.loadScene("LoginScene");
},
// update (dt) {},
});
[代码]
修改完后加载速度如图
[图片]
5.2 分包加载
除了启动页以外,其他所有代码都放到子包中加载(官方文档),启动页代码稍作修改
[代码]cc.Class({
extends: cc.Component,
properties: {
},
// LIFE-CYCLE CALLBACKS:
// onLoad () {},
start () {
cc.loader.downloader.loadSubpackage('subpackage', function (err) {
if (err) {
return console.error(err);
}
cc.director.loadScene("LoginScene");
});
},
// update (dt) {},
});
[代码]
修改完后:
5.3 尽快渲染-引擎加载前自绘制启动页
根据参考文档(https://forum.cocos.org/t/cocos-creator-50/94999)进行操作
1.自定义微信发布模板(定制项目构建流程),将首屏渲染代码及图片放到发布模板中,按照文档操作,项目根目录想创建目录build-templates,然后创建wechatgame目录
2.在 wechatgame 目录下新建 first-screen.js 脚本,拷贝以下内容:
[代码]
// Vertex shader program
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec2 a_TexCoord;\n' +
'varying vec2 v_TexCoord;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' v_TexCoord = a_TexCoord;\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'uniform sampler2D u_Sampler;\n' +
'varying vec2 v_TexCoord;\n' +
'void main() {\n' +
' gl_FragColor = texture2D(u_Sampler, v_TexCoord);\n' +
'}\n';
/**
* Create a program object and make current
* @param gl GL context
* @param vshader a vertex shader program (string)
* @param fshader a fragment shader program (string)
* @return true, if the program object was created and successfully made current
*/
function initShaders(gl, vshader, fshader) {
var program = createProgram(gl, vshader, fshader);
if (!program) {
console.log('Failed to create program');
return false;
}
gl.useProgram(program);
gl.program = program;
return true;
}
/**
* Create the linked program object
* @param gl GL context
* @param vshader a vertex shader program (string)
* @param fshader a fragment shader program (string)
* @return created program object, or null if the creation has failed
*/
function createProgram(gl, vshader, fshader) {
// Create shader object
var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
if (!vertexShader || !fragmentShader) {
return null;
}
// Create a program object
var program = gl.createProgram();
if (!program) {
return null;
}
// Attach the shader objects
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// Link the program object
gl.linkProgram(program);
// Check the result of linking
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
var error = gl.getProgramInfoLog(program);
console.log('Failed to link program: ' + error);
gl.deleteProgram(program);
gl.deleteShader(fragmentShader);
gl.deleteShader(vertexShader);
return null;
}
return program;
}
/**
* Create a shader object
* @param gl GL context
* @param type the type of the shader object to be created
* @param source shader program (string)
* @return created shader object, or null if the creation has failed.
*/
function loadShader(gl, type, source) {
// Create shader object
var shader = gl.createShader(type);
if (shader == null) {
console.log('unable to create shader');
return null;
}
// Set the shader program
gl.shaderSource(shader, source);
// Compile the shader
gl.compileShader(shader);
// Check the result of compilation
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
var error = gl.getShaderInfoLog(shader);
console.log('Failed to compile shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}
function initVertexBuffers(gl, vertices) {
var verticesTexCoords = vertices || new Float32Array([
// Vertex coordinates, texture coordinate
-1, 1, 0.0, 1.0,
-1, -1, 0.0, 0.0,
1, 1, 1.0, 1.0,
1, -1, 1.0, 0.0,
]);
var n = 4; // The number of vertices
// Create the buffer object
var vertexTexCoordBuffer = gl.createBuffer();
if (!vertexTexCoordBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
// Bind the buffer object to target
gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);
var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
//Get the storage location of a_Position, assign and enable buffer
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0);
gl.enableVertexAttribArray(a_Position); // Enable the assignment of the buffer object
// Get the storage location of a_TexCoord
var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord');
if (a_TexCoord < 0) {
console.log('Failed to get the storage location of a_TexCoord');
return -1;
}
// Assign the buffer object to a_TexCoord variable
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2);
gl.enableVertexAttribArray(a_TexCoord); // Enable the assignment of the buffer object
return n;
}
function initTextures(gl, n, imgPath) {
var texture = gl.createTexture(); // Create a texture object
if (!texture) {
console.log('Failed to create the texture object');
return false;
}
// Get the storage location of u_Sampler
var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler');
if (!u_Sampler) {
console.log('Failed to get the storage location of u_Sampler');
return false;
}
var image = wx.createImage(); // Create the image object
if (!image) {
console.log('Failed to create the image object');
return false;
}
// Register the event handler to be called on loading an image
image.onload = function () { loadTexture(gl, n, texture, u_Sampler, image); };
// Tell the browser to load an image
image.src = imgPath;
return true;
}
function loadTexture(gl, n, texture, u_Sampler, image) {
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // Flip the image's y axis
// Enable texture unit0
gl.activeTexture(gl.TEXTURE0);
// Bind the texture object to the target
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the texture parameters
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// Set the texture image
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
// Set the texture unit 0 to the sampler
gl.uniform1i(u_Sampler, 0);
gl.clear(gl.COLOR_BUFFER_BIT); // Clear <canvas>
gl.drawArrays(gl.TRIANGLE_STRIP, 0, n); // Draw the rectangle
}
function drawImg(imgPath) {
const vertices = new Float32Array([
-1, 1, 0.0, 1.0,
-1, -1, 0.0, 0.0,
1, 1, 1.0, 1.0,
1, -1, 1.0, 0.0,
]);
// Retrieve <canvas> element
// var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
// var gl = getWebGLContext(canvas);
const gl = wx.__first__canvas.getContext("webgl");
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Initialize shaders
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// Set the vertex information
var n = initVertexBuffers(gl, vertices);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
// Specify the color for clearing <canvas>
gl.clearColor(1.0, 1.0, 1.0, 1.0);
// Set texture
if (!initTextures(gl, n, imgPath)) {
console.log('Failed to intialize the texture.');
return;
}
exports.drawImg = drawImg;
[代码]
3.拷贝首屏图片到同目录下,重命名为 bg.png
4.创建一个脚本 game-backup.js ,拷贝以下内容:
“use strict”;
[代码]
// 渲染首屏(开发者工具执行有渲染问题,所以开发者工具上不渲染)
wx.__first__canvas = wx.createCanvas();
const data = wx.getSystemInfoSync();
if (data.platform != "devtools") {
const first_scene = require("./first-screen.js");
first_scene.drawImg("bg.png");
}
// 加载引擎代码写在这后面
[代码]
5.打开 Cocos Creator,点击右上角的编辑器进入编辑器安装目录
6.找到 builtin 目录,再依次进入adapters/platforms/wechat/wrapper/builtin 目录,打开 Canvas.js 文件入修改如下代码:
[代码]// 修改前
export default function Canvas() {
const canvas = wx.createCanvas()
// 修改后
let first = true
export default function Canvas() {
[代码]
8.先试一下能否正常运行,可以的话把 game.js 删除,用 game-backup.js 替换 game.js,game.js在替换的时候注意远程服务器地址配置(remoteDownloader.REMOTE_SERVER_ROOT = “”)需要根据实际情况修改
至此完成优化。