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

從零開發鴻蒙小遊戲——2048(下)

前言

在上一篇文章中咱們跟着張榮超老師的視頻課程學習了在鴻蒙設備上開發2048小遊戲的一部分,目前已經實現的功能有:頁面佈局、格子顯示、隨機增添格子、從新開始等。本文咱們將繼續學習2048遊戲的剩下功能。上篇文章連接:從零開發鴻蒙小遊戲2048(上)
共同窗習的小夥伴:
xxl_connorxian
RichardCwy
Les24601_
JE13543733623
yeswin411css

概述

在上一篇文章中咱們已經實現了2048小遊戲開始遊戲時的初始化,也就是咱們已經得到了2048遊戲每次開始時的狀態,咱們接下來的目標就是實現小遊戲的運行,讓遊戲真正的動起來。爲此咱們須要實現一個滑動事件,在每次滑動後屏幕中的格子會改變,同時咱們也須要有一個函數能夠判斷格子是否還有可滑動的方塊,來確保每次滑動是可行的,咱們也須要一個更新滑動後顯示的函數,以及更新當前分數的函數。這樣,基本的2048遊戲就完成了。接下來我會詳細講解上述函數的實現。canvas

項目的實現

滑動事件

在完成了遊戲開始的相關操做後,咱們剩下要完成的工做就是整個滑動事件了。在遊戲界面已有格子的狀況下滑動屏幕,格子會朝滑動方向移動,併合並相同的格子,而後在空位上隨機生成一個格子,並判斷遊戲是否結束。整個事件處理的邏輯很是直觀,其中增添新格子的功能咱們是已經實現的,咱們還沒有實現的還有格子的移動與合併函數以及遊戲的結束判斷函數。數組

dom

函數

移動格子併合並佈局

增添新格子學習

判斷遊戲是否結束字體

遊戲結束flex

首先咱們須要在index.hml文件中爲canvas組件添加一個onswipe屬性,令其屬性值爲咱們即將在index.js要寫的函數"swipeGrids"this

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

接着在index.js文件中增添函數swipeGrids,代碼以下:

swipeGrids(event) {
   
        let newGrids = this.changeGrids(event.direction);//根據方向移動場上格子,並將格子新的狀態賦給一個新數組
        if (newGrids.toString() != grids.toString()) {
   //若兩數組不一樣(格子發生了改變
            grids = newGrids;
            this.addTwoOrFourToGrids();//增添新的格子
            this.drawGrids();//繪製
           
            if (this.isGridsFull() == true && this.isGridsNotMergeable() == true) {
   //遊戲結束
                this.gameover();
            }
        }
    },

接着咱們來看changGrids函數的實現

移動與合併格子

在滑動前,須要定義一個新的4*4元素全爲零的數組newGrids用於記錄滑動後格子位置以及數值的狀況。
以向右滑動爲例。當咱們向右滑動後,遊戲界面上全部格子都須要向右移動,而全部格子共有四行,每一行有四個格子,咱們只須要按順序將每一行的格子向左移動併合並,整個遊戲界面就達成了總體向右滑動的目的。咱們先利用一個空數組array來按順序讀入那一行中非零元素的數值,這樣在數組array中全部格子都是相鄰的,中間不會有任何的空的格子。而後咱們再判斷相鄰格子是否有相等的,若是有,則將位置靠前的那個格子數值翻倍,同時將第二個格子變爲零。最後將整個array數組的非零元素按順序記錄到newGrids數組中,這樣就達成了移動併合並的操做。
在這裏插入圖片描述
注意:當事件爲向左滑動時array從左開始依次讀入grids的非零元,並從左邊把newGrids的元素值更改成array上的元素值,向右滑動則恰好相反。向上滑動與向下滑動相似。

具體代碼以下:

changeGrids(direction) {
   
        let newGrids = [[0, 0, 0, 0],
                        [0, 0, 0, 0],
                        [0, 0, 0, 0],
                        [0, 0, 0, 0]];

        if (direction == 'left' || direction == 'right') {
   
            let step = 1;
            if (direction == 'right') {
   
                step = -1;//step做爲循環時數組下標改變的方向
            }
            for (let row = 0; row < 4; row++) {
   //每一層
                let array = [];

                let column = 0;//若是爲left則從0開始right從3開始,
                if (direction == 'right') {
   
                    column = 3;
                }
                for (let i = 0; i < 4; i++) {
   
                    if (grids[row][column] != 0) {
   //把全部非零元依次放入數組中
                        array.push(grids[row][column]);
                    }
                    column += step;//當direction爲left時則從0向3遞增,爲right時則從3向0遞減
                }
                for (let i = 0; i < array.length - 1; i++) {
   //訪問當前元素及他的下一個元素,全部循環次數爲length-1
                    if (array[i] == array[i + 1]) {
   //判斷是否可合併,
                        array[i] += array[i + 1];//合併,
                        this.updateCurrentScores(array[i]);//更新分數
                        array[i + 1] = 0;//合併後參與合併的第二個元素消失
                        i++;
                    }
                }

                column = 0;
                if (direction == 'right') {
   
                    column = 3;
                }

                for (const elem of array) {
   
                    if (elem != 0) {
   //跳過array裏的空元素
                        newGrids[row][column] = elem;//把合併後的狀態賦給新數組grids,
                        column += step;
                    }
                }
            }
        } else if (direction == 'up' || direction == 'down') {
   //同理
            let step = 1;
            if (direction == 'down') {
   
                step = -1;
            }

            for (let column = 0; column < 4; column++) {
   
                let array = [];

                let row = 0;
                if (direction == 'down') {
   
                    row = 3;
                }
                for (let i = 0; i < 4; i++) {
   
                    if (grids[row][column] != 0) {
   
                        array.push(grids[row][column]);
                    }
                    row += step;
                }
                for (let i = 0; i < array.length - 1; i++) {
   
                    if (array[i] == array[i + 1]) {
   
                        array[i] += array[i + 1];
                        this.updateCurrentScores(array[i]);
                        array[i + 1] = 0;
                        i++;
                    }
                }
                row = 0;
                if (direction == 'down') {
   
                    row = 3;
                }
                for (const elem of array) {
   
                    if (elem != 0) {
   
                        newGrids[row][column] = elem;
                        row += step;
                    }
                }
            }
        }
        return newGrids;
    },

代碼中還有一個更新當前分數的函數,代碼以下:

updateCurrentScores(gridNum) {
   
        this.currentScores += gridNum;
    },

結束條件的判斷

當遊戲界面上格子已經滿了,而且不管怎麼滑動都沒有能夠合併的格子後,遊戲就結束了。判斷格子是不是滿的能夠經過查找grids數組中是否還有0來進行。當滿格子後判斷是否還有可合併格子則能夠經過遍歷整個grids數組分別判斷其元素是否與它相鄰的元素相等來判斷。
代碼以下:

isGridsFull() {
   
        if (grids.toString().split(",").indexOf("0") == -1) {
   //split() 方法用於把一個字符串分割成字符串數組。當找不到"0"時則說明全滿
            return true;
        } else {
   
            return false;
        }
    },
    isGridsNotMergeable() {
   
        for (let row = 0; row < 4; row++) {
   //遍歷一遍判斷每一個格子在行方向與列方向是否有可合併的
            for (let column = 0; column < 4; column++) {
   
                if (column < 3) {
   //判斷行方向上是否有可合併的格子只需前三列與它的下一列進行比較
                    if (grids[row][column] == grids[row][column + 1]) {
   //若當前格與行上下一格數字相同
                        return false;//一旦有可合併的函數就返回假,不用繼續判斷
                    }
                }
                if (row < 3) {
   //同理
                    if (grids[row][column] == grids[row + 1][column]) {
   
                        return false;
                    }
                }
            }
        }
        return true;//
    },

遊戲結束畫面

當場上格子滿了且沒法合併後,咱們但願遊戲界面能顯示遊戲結束的提示,而且場上格子的顏色褪色。
在這裏插入圖片描述
在index.hml文件中,咱們能夠將畫布組件canvas和一個用於顯示遊戲結束的文本組件放到一個棧組件中,同時文本在畫布的上方,在遊玩時設置爲不可見,當遊戲結束時將其設爲可見。同時能夠添加一個用於記錄褪色後格子數值對應顏色的字典,在遊戲結束時將變量colors改成褪色後的字典,再從新繪製一遍場上格子,就達成了使格子褪色的效果。
最後當咱們點擊從新開始時,咱們須要從新將遊戲結束文本設爲不可見,同時把colors改回原來的顏色字典,而後將當前分從新置零。具體的代碼在這裏就不進行展現了,想要了解能夠查看下方的源碼。
完成了上述工做後,咱們的2048遊戲就大致完成了。

源碼展現

index.hml

<div class="container">
    <text class="scores">
        最高分:{
  {bestScores}}
    </text>
    <text class="scores">
        當前分:{
  {currentScores}}
    </text>
    <stack class="stack">
        <canvas class="canvas" ref="canvas" onswipe="swipeGrids"></canvas>
        <div class="subcontainer" show="{ {isShow}}">
            <text class="gameover">
                遊戲結束
            </text>
        </div>
    </stack>
    <input type="button" value="從新開始" class="btn" onclick="restartGame"/>
</div>

index.css

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

index.js

var grids;
var context;

const THEME = {
   
    normal: {
   
        "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"
    },
    faded: {
   
        "0": "#D4C8BD",
        "2": "#EDE3DA",
        "4": "#EDE1D1",
        "8": "#F0CBAA",
        "16": "#F1BC9F",
        "32": "#F1AF9D",
        "64": "#F1A08B",
        "128": "#EDD9A6",
        "256": "#F6E5B0",
        "512": "#CDFF3F",
        "1024": "#CADCD4",
        "2048": "#75DBFF",
        "2or4": "#645B52",
        "others": "#FFFFFF"
    }
};
var colors = THEME.normal;

const SIDELEN = 70;
const MARGIN = 5;

export default {
   
    data: {
   
        bestScores: 9818,
        currentScores: 0,
        isShow: false
    },
    onInit() {
   
        this.initGrids();

        this.addTwoOrFourToGrids();
        this.addTwoOrFourToGrids();
    },
    initGrids() {
   
        grids = [[0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0]];
    },
    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: [[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]]

        // Math.random(): [0, 1)之間的小數
        // Math.random() * 16: [0, 16)之間的小數
        // Math.floor(x): 小於等於x的最大整數
        // Math.floor(Math.random() * 16):[0, 15]之間的整數
        // Math.floor(Math.random() * array.length):[0, array.length-1]之間的整數
        let randomIndex = Math.floor(Math.random() * array.length);
        let row = array[randomIndex][0];           //array至關於2*array.length的二維數組
        let column = array[randomIndex][1];        //得到第randomIndex個空位的橫縱座標
        if (Math.random() < 0.8) {
   
            grids[row][column] = 2;
        } else {
   
            grids[row][column] = 4;
        }
    },
    onReady() {
   
        context = this.$refs.canvas.getContext('2d');//得到2d繪製引擎將其賦值給變量context
    },
    onShow() {
   
        this.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;
                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);//繪製字體
                }
            }
        }
    },
    swipeGrids(event) {
   
        let newGrids = this.changeGrids(event.direction);//根據方向移動場上格子,並將格子新的狀態賦給一個新數組
        if (newGrids.toString() != grids.toString()) {
   //若兩數組不一樣(格子發生了改變
            grids = newGrids;
            this.addTwoOrFourToGrids();//增添新的格子
            this.drawGrids();//繪製

            if (this.isGridsFull() == true && this.isGridsNotMergeable() == true) {
   //遊戲結束
                this.gameover();
            }
        }
    },
    gameover(){
   
        colors = THEME.faded;
        this.drawGrids();
        this.isShow = true;
    },
    isGridsFull() {
   
        if (grids.toString().split(",").indexOf("0") == -1) {
   //split() 方法用於把一個字符串分割成字符串數組。當找不到"0"時則說明全滿
            return true;
        } else {
   
            return false;
        }
    },
    isGridsNotMergeable() {
   //用於判斷結束條件
        for (let row = 0; row < 4; row++) {
   //遍歷一遍判斷每一個格子在行方向與列方向是否有可合併的
            for (let column = 0; column < 4; column++) {
   
                if (column < 3) {
   //判斷行方向上是否有可合併的格子只需前三列與它的下一列進行比較
                    if (grids[row][column] == grids[row][column + 1]) {
   //若當前格與行上下一格數字相同
                        return false;//一旦有可合併的函數就返回假,不用繼續判斷
                    }
                }
                if (row < 3) {
   //同理
                    if (grids[row][column] == grids[row + 1][column]) {
   
                        return false;
                    }
                }
            }
        }
        return true;//
    },
    changeGrids(direction) {
   
        let newGrids = [[0, 0, 0, 0],
                        [0, 0, 0, 0],
                        [0, 0, 0, 0],
                        [0, 0, 0, 0]];

        if (direction == 'left' || direction == 'right') {
   
            let step = 1;
            if (direction == 'right') {
   
                step = -1;//step做爲循環時數組下標改變的方向
            }
            for (let row = 0; row < 4; row++) {
   //每一層
                let array = [];

                let column = 0;//若是爲left則從0開始right從3開始,
                if (direction == 'right') {
   
                    column = 3;
                }
                for (let i = 0; i < 4; i++) {
   
                    if (grids[row][column] != 0) {
   //把全部非零元依次放入數組中
                        array.push(grids[row][column]);
                    }
                    column += step;//當direction爲left時則從0向3遞增,爲right時則從3向0遞減
                }
                for (let i = 0; i < array.length - 1; i++) {
   //訪問當前元素及他的下一個元素,全部循環次數爲length-1
                    if (array[i] == array[i + 1]) {
   //判斷是否可合併,
                        array[i] += array[i + 1];//合併,
                        this.updateCurrentScores(array[i]);//更新分數
                        array[i + 1] = 0;//合併後參與合併的第二個元素消失
                        i++;
                    }
                }

                column = 0;
                if (direction == 'right') {
   
                    column = 3;
                }

                for (const elem of array) {
   
                    if (elem != 0) {
   //跳過array裏的空元素
                        newGrids[row][column] = elem;//把合併後的狀態賦給新數組grids,
                        column += step;
                    }
                }
            }
        } else if (direction == 'up' || direction == 'down') {
   
            let step = 1;
            if (direction == 'down') {
   
                step = -1;
            }

            for (let column = 0; column < 4; column++) {
   
                let array = [];

                let row = 0;
                if (direction == 'down') {
   
                    row = 3;
                }
                for (let i = 0; i < 4; i++) {
   
                    if (grids[row][column] != 0) {
   
                        array.push(grids[row][column]);
                    }
                    row += step;
                }
                for (let i = 0; i < array.length - 1; i++) {
   
                    if (array[i] == array[i + 1]) {
   
                        array[i] += array[i + 1];
                        this.updateCurrentScores(array[i]);
                        array[i + 1] = 0;
                        i++;
                    }
                }
                row = 0;
                if (direction == 'down') {
   
                    row = 3;
                }
                for (const elem of array) {
   
                    if (elem != 0) {
   
                        newGrids[row][column] = elem;
                        row += step;
                    }
                }
            }
        }
        return newGrids;
    },
    updateCurrentScores(gridNum) {
   
        this.currentScores += gridNum;
    },
    restartGame() {
   
        this.currentScores = 0;
        this.isShow = false;
        colors = THEME.normal;

        this.initGrids();

        this.addTwoOrFourToGrids();
        this.addTwoOrFourToGrids();

        this.drawGrids();
    }
}

尾聲

以上即是咱們關於鴻蒙2048小遊戲的所有學習筆記了。咱們所學習的遊戲源碼是由張榮超老師所編寫,咱們在張老師源碼的基礎上進行學習並復現,最終經過寫這篇解讀性博客的形式,來加深本身的記憶。也但願這篇文章能使更多的和咱們同樣的學習者看到了能有所收穫,若是有大佬看見了也但願大佬可以斧正。最後再次感謝張榮超老師的教程、以及歐JS師兄的引領、王bh老師的教導。願共勉之。

相關文章
相關標籤/搜索