1. 程式人生 > >微信小遊戲原生排行版的實現

微信小遊戲原生排行版的實現

微信小遊戲原生排行版的實現

在之前微信小遊戲子域踩坑記錄中就已經提到過包體過大的問題了,現在這個問題終於爆了,今天花了一天的時間使用原生介面重寫了之前的子域工程,也算是償還技術債務了。使用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

參考

官方文件

社群大佬分享