微信小游戏原生排行版的实现
在之前微信小游戏子域踩坑记录中就已经提到过包体过大的问题了,现在这个问题终于爆了,今天花了一天的时间使用原生接口重写了之前的子域工程,也算是偿还技术债务了。使用creator1.9.3打包出来的子域工程大小为755kb,而使用原生排行版只要占用50kb不到的空间,包括所有美术资源。
基本流程
先来聊一聊开放数据域如何显示:在小游戏环境下,关系链数据只能在一个封闭的子域中获取,子域Canvas无法直接显示,只能被绘制到上屏Canvas上。上屏Canvas能向sharedCanvas发送消息,反之不行。
- 上屏Canvas发送显示指令
- sharedCanvas接收指令,并渲染相关关系链数据
- 上屏Canvas将sharedCanvas的内容绘制出来
显示层级
Canvas中后绘制的会在上面,所以需要注意绘制的顺序,否则会出现被遮挡的情况。另外由于image的加载是异步的,所以我使用Promise对所有的image绘制进行封装。将所有的渲染分成两个部分:异步的和同步的。
renderImages(){
if(!this.isVisible()){
return [];
}
let promises = [];
promises.push(this.drawImage('friendContent', 0, 0, 640, 128));
let rankSrc;
switch (this.info.rank) {
case 1:
rankSrc = 'first';
break;
case 2:
rankSrc = 'second';
break;
case 3:
rankSrc = 'third';
break;
default:
rankSrc = 'others';
break;
}
promises.push(this.drawImage(rankSrc, -10, -20));
promises.push(this.drawImage('friendIconOn', 376, 22));
promises.push(this.drawImage('starNum', 156, 80));
if(this.info.avatarUrl){
promises.push(this.drawImage(this.info.avatarUrl, 30, 17, 88, 88, true));
}else{
promises.push(this.drawImage('friendDefaultUserIcon', 30, 17, 88, 88));
}
return promises;
}
renderTexts(){
if (!this.isVisible()) {
return;
}
this.drawText(this.info.name, 156, 54, 24, '#A46E63', 'left');
this.drawText(this.info.starNum, 262, 95, 18, '#A46E63', 'left');
this.drawText(this.info.rank, 10, 10, 24, null, 'center');
}
在调用的时候,图片都是在最下方的,文本由于需要显示信息,必然可以在所有图片绘制完成后绘制。上面的代码是一个排行版item的绘制方法,我们会先调用图片绘制方法,获得所有的promise,并且真正绘制,最后调用同步绘制方法。
render(){
for(const item of this.items){
item.setPosition((this.canvas.width - 640) / 2, this.deltaY);
}
const allDrawPromises = [];
for (const item of this.items) {
const promises = item.renderImages();
for(let promise of promises){
allDrawPromises.push(promise);
}
}
Promise.all(allDrawPromises).then((drawInfos) => {
this.clear();
this.renderBg();
for (const info of drawInfos) {
if (info.width) {
this.ctx.drawImage(info.img, info.left, info.top, info.width, info.height);
} else {
this.ctx.drawImage(info.img, info.left, info.top);
}
}
for (const item of this.items) {
item.renderTexts();
}
}).catch((err) => console.warn(err));
}
这样我们就可以保证了显示的层级
滚动实现
滚动的实现也很简单,在主域Canvas显示子域信息的区域监听TOUCH_MOVE事件,然后向子域发送Scroll消息,然后子域按照消息绘制。这里我们只谈子域的实现。我们首先需要保存一个全局偏移信息,然后再绘制的时候加上这个偏移,并且保证偏移的上下限。在接收到消息后,我们修改这个偏移,并且重新绘制子域。
scroll(info){
if(this.deltaY - info.y >= this.maxY){
return;
}
if (this.deltaY - info.y - this.canvas.height <= this.minY){
return;
}
this.deltaY -= info.y;
this.deltaY = Math.max(this.minY, Math.min(this.maxY, this.deltaY));
this.render();
}
image不要重复创建
你需要一个image缓存,而不是每次绘制都重新等待image的加载。我们的绘制会很频繁,如果每次绘制都重新加载image会很卡!!!!而且会占用额外的内存,所以仅在第一次绘制的时候加载image
仅绘制必要的部分
如果一个item不会显示出来,那么你不就不应该绘制它。这个可以通过你item的index(排名)和Canvas的高度(仅考虑上下滑动)以及偏移量来判定
setPosition(left, top){
top = top + (ITEM_HEIGHT + ITEM_SPACINGY) * (this.info.rank - 1);
this.left = left;
this.top = top;
}
isVisible(){
if (this.top >= this.canvas.height || this.top <= -ITEM_HEIGHT) {
return false;
}else{
return true;
}
}