[深大深鴻會]利用DevEco Studio從零開發OpenHarmony小遊戲——2048(上)

前言

最近與小夥伴一塊兒跟着張榮超老師的視頻課程《從零開發鴻蒙小遊戲App》學習了許久,受益不淺。爲了更好的掌握所學的知識,咱們在這裏寫下這篇文章,用於記錄學習過程當中理解與感悟,也分享給更多和我同樣的鴻蒙初學者,但願在這個過程當中可以相互交流、共同進步。本文也是咱們第一次寫的博客,若是在內容上或排版上有什麼好的建議,歡迎向我提出來。
共同窗習的小夥伴:
xxl_connorxian
RichardCwy
Les24601_
JE13543733623
yeswin411javascript

概述

本次課程將從零開始實現一個運行在鴻蒙設備上的經典小遊戲2048。本文所實現的功能有:頁面佈局、顯示格子與數字、遊戲頁面的初始化。後續的功能會寫在下一篇文章中。css

  1. 課程使用的IDE爲華爲的DevEco Studio
  2. 課程開發的應用將運行在虛擬設備Huawei Lite Wearable上
  3. 課程但願實現的app爲小遊戲2048
  4. 課程所使用的語言爲js

2048遊戲規則簡要介紹

  1. 開始遊戲時隨機兩個格子上會出現2或4
    在這裏插入圖片描述
  2. 上下左右滑動進行操做,畫面中的數字會朝相應方向滑動,相同的數字滑到一塊兒則數字會相加,而後隨機在一個空格子上生成2或4
    向左滑動後
  3. 若格子全滿且沒法再經過滑動合併格子則遊戲結束
    在這裏插入圖片描述

建立項目

建立新的項目文件:點擊左上角file,點擊new,選擇Lite Wearable選項,選擇默認的模板(如圖),而後將文件命名爲Game2048(注意,文件名的路徑中不要出現中文,不然沒法建立新項目)。html

建立完成後,能夠看見左側的資源管理器中有有以下文件:java

在這裏插入圖片描述
咱們須要編程的地方主要在index.css、index.hml、index.js這三個文件上。其中css負責肯定組件的樣式(Presentation),hml決定頁面的結構(Structure),js負責控制組件的行爲( Behavior)。編程

項目的實現

頁面佈局

首先咱們但願實現以下佈局
在這裏插入圖片描述
其中最高分與當前分的顯示採用動態綁定的方式,在index.js中設置初始值9818與0。canvas

hml代碼以下:數組

<div class="container">
    <text class="scores">
        最高分: {
  {bestScores}}
    </text>
    <text class="scores">
        當前分:{
  {currentScores}}
    </text>
    <canvas class="canvas">
    </canvas>
    <input type="button" value="從新開始" class="btn"/>
</div>

樣式設置:app

.container { 
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 454px;
    height: 454px;
}
.canvas { 
    width: 305px;
    height: 305px;
    background-color: #BBADA0;
}
.btn { 
    width: 150px;
    height: 30px;
    background-color: #AD9D8F;
    font-size: 24px;
    margin-top: 10px;
}
.scores { 
	font-size: 18px;
	text-align: center;
	width: 300px;
	height: 20px;
	letter-spacing: 0px;
	margin-top: 10px;
}

在畫布上顯示全部格子與對應數字

目標如圖:
在這裏插入圖片描述
1 首先咱們在index.js文件中定義一個二維數組,用於存放各格子內的數字dom

var grids=[[0,2,4,8],
           [16,32,64,128],
           [256,512,1024,2048],
           [8,4,2,0]];

爲了將數字與顏色對應起來,咱們構建一個字典用於存放不一樣數字下對應的顏色值函數

const COLOR={ 
    "0": "#CDC1B4",
    "2": "#EEE4DA",
    "4": "#EDE0C8",
    "8": "#F2B179",
    "16": "#F59563",
    "32": "#F67C5F",
    "64": "#F65E3B",
    "128": "#EDCF72",
    "256": "#EDCC61",
    "512": "#99CC00",
    "1024": "#83AF9B",
    "2048": "#0099CC",
    "2or4": "#645B52",
    "others": "#FFFFFF"
}

2 接着咱們考慮在index.js中寫一個drawGrids函數。爲調用canvas組件的繪圖引擎,咱們首先要在index.hml文件中爲canvas組件添加屬性ref並令其值爲"canvas",

<canvas class="canvas" ref="canvas"></canvas>

接着咱們在index.js文件中重寫生命週期事件onReady(),在其中得到canvas組件的對象實例並調用函數getContext(「2d」),將其賦值給變量context。其中context可能會在其餘函數中被用到,故咱們須要提早聲明一個全局變量context

onReady() { 
        context = this.$refs.canvas.getContext('2d');//得到2d繪製引擎將其賦值給變量context
    },

3 drawGrids的實現:

drawGrids() { 
        for (let row = 0; row < 4; row++) { 
            for (let column = 0; column < 4; column++) { 
                let gridStr = grids[row][column].toString();//得到當前格子數字的字符串
				/*方格的繪製*/
                context.fillStyle = colors[gridStr]; //繪圖填充顏色
                let leftTopX = column * (MARGIN + SIDELEN) + MARGIN;//MARGIN與SIDELEN爲常量,對應值爲5 70
                let leftTopY = row * (MARGIN + SIDELEN) + MARGIN;//
                context.fillRect(leftTopX, leftTopY, SIDELEN, SIDELEN);
				
				/*字體的繪製*/
                context.font = "24px HYQiHei-65S";//設置字體
                if (gridStr != "0") { 
                    if (gridStr == "2" || gridStr == "4") { 
                        context.fillStyle = colors["2or4"];//字體顏色
                    } else { 
                        context.fillStyle = colors["others"];
                    }

                    let offsetX = (4 - gridStr.length) * (SIDELEN / 8);//調整字體位置
                    let offsetY = (SIDELEN - 24) / 2;
                    context.fillText(gridStr, leftTopX + offsetX, leftTopY + offsetY);//繪製字體
                }
            }
        }
    },

完成以上步驟後,在index.js中重寫生命週期事件onShow(),在其中調用函數drawGrids(),便可實現本節的目標。

頁面初始化

在實現滑動界面使格子移動以前,咱們須要先實現頁面的初始化,即在每次遊戲開始時,在16個空的格子中隨機選擇兩個格子增長數字2或4,其中2出現的概率比4大。爲此,咱們須要在生命週期事件onInit()中給元素全爲0的grids數組隨機選取兩個元素使它的值變爲2或4,而後再調用drawGrids()顯示結果。咱們編寫一個用於在空格子中隨機選擇一個格子並增添2或4的函數addTwoOrFourToGrids(),在onInit()中調用兩次便可實現隨機選取兩個格子的目的。
addTwoOrFourToGrids()代碼以下:

addTwoOrFourToGrids(){ 
        let array=[];//建立一個數組用於存放空格子的位置信息
        for(let row =0;row<4;row++){ 
            for(let column=0;column<4;column++){ 
                if(grids[row][column]==0){ //當格子爲空時
                    array.push([row,column]);//將空格子位置信息的數組放入array中
                }
            }
        }
        // array: [[0, 0], [0, 1], [0, 2], [0, 3], [1, 0], [1, 1], [1, 2], [1, 3], [2, 0], [2, 1], [2, 2], [2, 3], [3, 0], [3, 1], [3, 2], [3, 3]]
        let randomIndes=Math.floor(Math.random()*array.length);//[0, array.length-1]之間的整數
        let row=array[randomIndes][0];
        let column=array[randomIndes][1];
        if(Math.random()<0.8){ //2出現的機率比4大
            grids[row][column]=2;
        }else{ 
            grids[row][column]=4;
        }
    },

爲了代碼的簡潔與直觀,咱們能夠編寫一個initGrids()函數用於初始化數組grids,這樣在聲明變量時就不用再給grids賦值,同時在其餘函數中須要從新初始化grids時也不用再次賦值

initGrids(){ 
        grids=[[0,0,0,0],
               [0,0,0,0],
               [0,0,0,0],
               [0,0,0,0]];
    },

這樣onInit()函數就實現了初始化遊戲界面的目的

onInit(){ 
        this.initGrids();
        this.addTwoOrFourToGrids();
        this.addTwoOrFourToGrids();
    },

爲了方便測試,咱們接着實現從新開始的功能。
在index.hml文件中爲button組件添加屬性onclick,編寫一個函數restartGame()爲onclick的屬性值,在restartGame()中實現從新開始的目的

<input type="button" value="從新開始" class="btn" onclick="restartGame"/>
restartGame(){ 
        this.initGrids();
        this.addTwoOrFourToGrids();
        this.addTwoOrFourToGrids();
        this.drawGrids();
    }

到這裏咱們就實現了開始時隨機選取兩空格填入2或4,以及遊戲的重啓功能。運行結果以下
在這裏插入圖片描述

代碼展現

index.js

var grids;
var context;
const colors={ 
    "0": "#CDC1B4",
    "2": "#EEE4DA",
    "4": "#EDE0C8",
    "8": "#F2B179",
    "16": "#F59563",
    "32": "#F67C5F",
    "64": "#F65E3B",
    "128": "#EDCF72",
    "256": "#EDCC61",
    "512": "#99CC00",
    "1024": "#83AF9B",
    "2048": "#0099CC",
    "2or4": "#645B52",
    "others": "#FFFFFF"
}
const MARGIN =5;
const SIDELEN=70;
export default { 
    data: { 
        currentScores: 0,
        bestScores: 9818
    },
    onInit(){ 
        this.initGrids();
        this.addTwoOrFourToGrids();
        this.addTwoOrFourToGrids();
    },
    onReady(){ 
        context=this.$refs.canvas.getContext("2d");
    },
    onShow(){ 
        this.drawGrids();
    },
    initGrids(){ 
        grids=[[0,0,0,0],
               [0,0,0,0],
               [0,0,0,0],
               [0,0,0,0]];
    },
    drawGrids() { 
        for (let row = 0; row < 4; row++) { 
            for (let column = 0; column < 4; column++) { 
                let gridStr = grids[row][column].toString();

                context.fillStyle = colors[gridStr]; //繪圖填充顏色
                let leftTopX = column * (MARGIN + SIDELEN) + MARGIN;
                let leftTopY = row * (MARGIN + SIDELEN) + MARGIN;
                context.fillRect(leftTopX, leftTopY, SIDELEN, SIDELEN);

                context.font = "24px HYQiHei-65S";//設置字體
                if (gridStr != "0") { 
                    if (gridStr == "2" || gridStr == "4") { 
                        context.fillStyle = colors["2or4"];//字體顏色
                    } else { 
                        context.fillStyle = colors["others"];
                    }

                    let offsetX = (4 - gridStr.length) * (SIDELEN / 8);
                    let offsetY = (SIDELEN - 24) / 2;
                    context.fillText(gridStr, leftTopX + offsetX, leftTopY + offsetY);//繪製字體
                }
            }
        }
    },
    addTwoOrFourToGrids(){ 
        let array=[];
        for(let row =0;row<4;row++){ 
            for(let column=0;column<4;column++){ 
                if(grids[row][column]==0){ 
                    array.push([row,column]);
                }
            }
        }
        let randomIndes=Math.floor(Math.random()*array.length);
        let row=array[randomIndes][0];
        let column=array[randomIndes][1];
        if(Math.random()<0.8){ 
            grids[row][column]=2;
        }else{ 
            grids[row][column]=4;
        }
    },
   /* swipeGrids(event){ let newGrids; if(newGrids.toString()!=grids.toString()){ grids=newGrids; this.addTwoOrFourToGrids(); this.drawGrids(); } }*/
    restartGame(){ 
        this.initGrids();
        this.addTwoOrFourToGrids();
        this.addTwoOrFourToGrids();
        this.drawGrids();
    }

}

index.css

.container { 
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 454px;
    height: 454px;
}
.canvas { 
    width: 305px;
    height: 305px;
    background-color: #BBADA0;
}
.btn { 
    width: 150px;
    height: 30px;
    background-color: #AD9D8F;
    font-size: 24px;
    margin-top: 10px;
}
.scores { 
    font-size: 18px;
    text-align: center;
    width: 300px;
    height: 20px;
    letter-spacing: 0px;
    margin-top: 10px;
}

index.hml

<div class="container">
    <text class="scores">
        最高分:{
  {bestScores}}
    </text>
    <text class="scores">
        當前分:{
  {currentScores}}
    </text>
    <canvas class="canvas" ref="canvas" onswipe="swipeGrids">
    </canvas>
    <input type="button" value="從新開始" class="btn" onclick="restartGame"/>
</div>

結語

到這裏就是張榮超老師課程目前已實現的內容了,接下來還未實現的功能有滑動格子和格子的合併、分數的計算以及遊戲結束的判斷。我會繼續對張老師給出的源代碼進行學習與解讀學習其餘功能的實現,並將學習的內容寫在下一篇文章中。

相關文章
相關標籤/搜索