微信小游戏原生排行版的实现

微信小游戏原生排行版的实现

在之前微信小游戏子域踩坑记录中就已经提到过包体过大的问题了,现在这个问题终于爆了,今天花了一天的时间使用原生接口重写了之前的子域工程,也算是偿还技术债务了。使用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;
    }
}

源码

点这里,记得给小星星O

参考

官方文档

社区大佬分享

扫描二维码关注公众号,回复: 4225812 查看本文章

猜你喜欢

转载自my.oschina.net/u/3905200/blog/1931047