從零開發HarmonyOS(鴻蒙)運動手錶小遊戲——數字華容道

前言

這次博客是深鴻會博主(Zzt_01-23)學習完HarmonyOS(鴻蒙)後,自行開發的第二個鴻蒙demo——數字華容道,詳細講述了數字華容道的開發思路,適合剛入門小白,使讀者能自行從零開發一個小遊戲APP——數字華容道,文末附有整個demo的源代碼,有興趣的讀者也能夠前往觀看個人第一個遊戲開發學習:黑白翻棋,較第一個而言,這次只是多了滑動事件和計時器,其餘組件和第一個遊戲同樣,建議先觀看完第一個再學習第二個。javascript

概述

本個demo將從零基礎開始完成鴻蒙小遊戲APP在可穿戴設備上的編譯,此處以運動手錶爲例,在項目中咱們所使用到的軟件爲DevEco Studio,下載地址爲:DevEco Studio下載DevEco Studio安裝教程,在項目中咱們要實現的內容爲數字華容道APP的開發。css

  1. 在初始界面中顯示4*4的方陣,方陣中分佈有隨意打亂的1至15的數字和一個空白方格,方陣上方增長一個計時器,顯示遊戲進行的時間,單位爲秒,方陣下方顯示一個「從新開始」的按鈕,爲用戶提供從新開始遊戲的機會。
    在這裏插入圖片描述java

  2. 向上、下、左、右任一方向滑動,空白方格周圍對應位置的方格便會隨之向對應的方向移動一格,計時器也會顯示遊戲開始到當前的時間。
    在這裏插入圖片描述算法

  3. 通過若干次移動後,當全部的數字按順序排列後,則會彈出遊戲成功的界面,再滑動也不會有任何變化,此時方陣上方的計時器中止計時,點擊「從新開始」的按鈕後則會從新返回步驟1界面所示。
    在這裏插入圖片描述canvas

正文

建立項目

DevEco Studio下載安裝成功後,打開DevEco Studio,點擊左上角的File,點擊New,再選擇New Project,選擇Lite Wearable選項,選擇默認的模板,而後選擇保存路徑,將文件命名爲Game1(文件名不能出現中文或者特殊字符,不然將沒法成功建立項目文件),最後點擊Finish。
在這裏插入圖片描述
主要編寫的文件爲index.css、index.hml和index.js,打開路徑如圖所示,index.hml用於描述頁面中包含哪些組件,index.css用於描述頁面中的組件都長什麼樣,index.js用於描述頁面中的組件是如何進行交互的。
在這裏插入圖片描述數組

實現開始界面的佈局

首先,咱們要先畫出一個4*4的方陣,方陣中按照順序顯示1至15的數字,方陣上方顯示「當前秒數:0」,方陣下方有一個「從新開始」的按鈕。
在這裏插入圖片描述
1.在index.hml中添加相應的頁面組件:
首先建立一個基礎容器div類名爲container,用於盛裝全部的其餘組件app

<div class="container" >
</div>

而後在此基礎容器中添加一個文字組件text類名爲seconds,且註明顯示的固定部分「當前秒數:」,爲動態變換部分賦予一個名爲currentSteps的變量,用於動態顯示遊戲進行的秒數dom

<text class="seconds">
        當前秒數:{ { currentSeconds}}
</text>

再添加一個畫布組件canvas類名爲canvas,增長一個引用屬性ref,用來指定指向子元素或子組件的引用信息,該引用將註冊到父組件的$refs 屬性對象上,以便在此畫布上畫出4*4的方陣ide

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

最後添加一個普通按鈕組件,類名爲bit,並賦值「從新開始」,用於從新開始遊戲函數

<input type="button" value="從新開始" class="bit"/>

至此,index.hml文件已經所有編寫完畢

<div class="container" >
    <text class="seconds">
        當前秒數:{ { currentSeconds}}
    </text>
    <canvas class="canvas" ref="canvas" ></canvas>
    <input type="button" value="從新開始" class="bit"/>
</div>

2.在index.css中描述剛纔添加的頁面組件的樣式:
首先編寫container的樣式,flex-direction爲容器主軸方向,選擇column(垂直方向從上到下),justify-content爲容器當前行的主軸對齊格式,選擇center(項目位於容器的中心),align-items爲容器當前行的交叉軸對齊格式,選擇center(元素在交叉軸居中),width、height分別爲容器以像素爲單位的寬度和高度,都設定爲450px

.container { 
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width:450px;
    height:450px;
}

而後編寫seconds的樣式,font-size爲設置文本的尺寸,設定爲18px,text-align爲設置文本的文本對齊方式,選擇center(文本居中對齊),width、height分別設定爲300px和20px,letter-spacing爲設置文本的字符間距,設定爲0px,margin-top爲設置上外邊距,設定爲10px

.seconds{ 
    font-size: 18px;
    text-align:center;
    width:300px;
    height:20px;
    letter-spacing:0px;
    margin-top:10px;
}

再編寫canvas的樣式,width、height都設定爲320px,background-color爲設置背景顏色,設定爲#FFFFFF

.canvas{ 
    width:305px;
    height:305px;
    background-color: #FFFFFF;
}

最後編寫bit的樣式,width、height分別設定爲150px和30px,background-color設定爲#AD9D8F,font-size設定爲24px,margin-top設定爲10px

.bit{ 
    width:150px;
    height:30px;
    background-color:#AD9D8F;
    font-size:24px;
    margin-top:10px;
}

至此,index.css文件已經所有編寫完畢
3.在index.js中描述頁面中的組件交互狀況:
首先在data中爲當前秒數currentSeconds賦值爲0

data: { 
        currentSeconds:0,
    }

而後在文件開頭定義一個全局變量context,定義一個全局變量的二維數組grids,其中的值爲1至15和0,定義全局常量方格的邊長SIDELEN爲70,方格的間距MARGIN爲5,建立一個onReady()函數,用於定義2d繪畫工具

var grids=[[1, 2, 3, 4],
           [5, 6, 7, 8],
           [9, 10, 11, 12],
           [13, 14, 15, 0]];
var context;

const SIDELEN = 70;
const MARGIN = 5;

onReady(){ 
        context=this.$refs.canvas.getContext('2d');
    }

再建立drawGrids()函數,先將grids的值利用toString()函數所有轉化爲字符串,fillStyle爲畫圖工具context的方格的顏色,方格的背景顏色定義爲#BBADA0,數字的顏色定義爲#000000,fillRect爲畫圖工具context的畫圖矩形的大小,其中有四個參數,第一個參數指定矩形左上角的x座標,第二個參數指定矩形左上角的y座標,第三個參數指定矩形的高度,第四個參數指定矩形的寬度。font爲爲畫圖工具context的字體大小,定義爲30px HYQiHei-65S,由於要出現一個空白的方格,因此須要添加一個判斷語句,當數字爲0時不顯示數字。fillText爲畫圖工具context的文本字體大小,其中有三個參數,第一個參數爲繪製的文本,第二個參數指定文本左上角的x座標,第三個參數指定文本左上角的y座標,最後建立onShow()函數,用於調用drawGrids()函數

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 = "#BBADA0";
                let leftTopX = column * (MARGIN + SIDELEN) + MARGIN;
                let leftTopY = row * (MARGIN + SIDELEN) + MARGIN;
                context.fillRect(leftTopX, leftTopY, SIDELEN, SIDELEN);

                context.font = "30px HYQiHei-65S";
                if (gridStr != "0") { 
                    context.fillStyle = "#000000";
                    let offsetX = (4 - gridStr.length) * (SIDELEN / 8);
                    let offsetY = (SIDELEN - 30) / 2;
                    context.fillText(gridStr, leftTopX + offsetX, leftTopY + offsetY);
                }
            }
        }
    }

至此,index.jd文件已經所有編寫完畢,運行便可得出上述界面佈局了

var grids=[[1, 2, 3, 4],
           [5, 6, 7, 8],
           [9, 10, 11, 12],
           [13, 14, 15, 0]];
var context;

const SIDELEN = 70;
const MARGIN = 5;

export default { 
    data: { 
        currentSeconds:0,
    },
    onReady() { 
        context = this.$refs.canvas.getContext('2d');
    },
    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 = "#BBADA0";
                let leftTopX = column * (MARGIN + SIDELEN) + MARGIN;
                let leftTopY = row * (MARGIN + SIDELEN) + MARGIN;
                context.fillRect(leftTopX, leftTopY, SIDELEN, SIDELEN);

                context.font = "30px HYQiHei-65S";
                if (gridStr != "0") { 
                    context.fillStyle = "#000000";
                    let offsetX = (4 - gridStr.length) * (SIDELEN / 8);
                    let offsetY = (SIDELEN - 30) / 2;
                    context.fillText(gridStr, leftTopX + offsetX, leftTopY + offsetY);
                }
            }
        }
    },
}

實現數字的隨機打亂和方格的移動

其次咱們要在屏幕上隨機生成一個數字被隨意打亂的4*4的方陣,而且向任意方向滑動屏幕,空白方格周圍對應位置的方格便會隨之向對應的方向移動一格
在這裏插入圖片描述
在這裏插入圖片描述
1.在index.hml中添加相應的頁面組件:
咱們須要在畫布中添加一個swipe屬性,用於響應滑動事件,賦予一個所自動調用的函數swipeGrids

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

至此,index.hml文件已經所有編寫完畢
2.在index.css中描述剛纔添加的頁面組件的樣式:
這一部分不須要添加或修改頁面組件的樣式
3.在index.js中描述頁面中的組件交互狀況:
首先咱們得先實現方格的移動,建立一個函數changeGrids(direction),接受一個參數direction指示滑動的方向,先找出空白方格所在位置對應的二維數組下標,接着判斷參數direction爲’left’、‘right’、‘up’ 或’down’時,對應的方格和空白方格對應的二維數組的數值對調,建立響應滑動事件所自動調用的函數swipeGrids(event),參數爲滑動事件,調用函數changeGrids(direction),並傳入滑動的方向,最後調用函數drawGrids()

swipeGrids(event) { 
        this.changeGrids(event.direction);
        this.drawGrids();
    },
    changeGrids(direction) { 
        let x;
        let y;
        for (let row = 0; row < 4; row++) { 
            for (let column = 0; column < 4; column++) { 
                if (grids[row][column] == 0) { 
                    x = row;
                    y = column;
                    break;
                }
            }
        }
        let temp;
        if (direction == 'left' && (y + 1) < 4) { 
            temp = grids[x][y + 1];
            grids[x][y + 1] = grids[x][y];
            grids[x][y] = temp;
        } else if (direction == 'right' && (y - 1) > -1) { 
            temp = grids[x][y - 1];
            grids[x][y - 1] = grids[x][y];
            grids[x][y] = temp;
        } else if (direction == 'up' && (x + 1) < 4) { 
            temp = grids[x + 1][y];
            grids[x + 1][y] = grids[x][y];
            grids[x][y] = temp;
        } else if (direction == 'down' && (x - 1) > -1) { 
            temp = grids[x - 1][y];
            grids[x - 1][y] = grids[x][y];
            grids[x][y] = temp;
        }
    }

而後添加一個函數initGrids(),用於隨機打亂排列規則的數字,先建立一個一維數組變量array,賦值爲上下左右四個方向"left",「up」,「right」,「down」,Math.random()函數是隨機[0,1)內的小數,Math.random() * 4是隨機[0,4)內的小數,Math.floor(x)爲得出小於或等於x的最大整數,隨機生成一個數字,讀取數組array對應的值,調用函數this.changeGrids(direction)並將剛纔的array對應的值傳入,便能移動一次方格,循環此步驟若干次即可隨機打亂排列規則的數字,生成一個數字被隨意打亂的4*4的方陣

initGrids(){ 
        let array=["left","up","right","down"];

        for (let i = 0; i < 100; i++){ 
            let randomIndex = Math.floor(Math.random() * 4);
            let direction = array[randomIndex];
            this.changeGrids(direction);
        }
    }

最後在函數onShow()中調用函數initGrids()

onShow() { 
        this.initGrids();
        this.drawGrids();
    }

至此,index.jd文件已經所有編寫完畢,運行便可得出上述界面佈局了

實現計時器、從新開始和遊戲成功

最後咱們要在方陣上方動態顯示遊戲開始到當前的時間,而且當數字按順序排列後彈出「遊戲成功」界面,點擊「從新開始」按鈕可以從新隨機生成一個數字被隨意打亂的4*4的方陣,當前秒數置爲0 ,且可以從新開始計時
在這裏插入圖片描述
在這裏插入圖片描述
1.在index.hml中添加相應的頁面組件:
爲了使數字按順序排列後才顯示「遊戲成功」界面,須要添加一個棧stack類名設定爲stack,使畫布先進棧,「遊戲成功」後進棧,這樣就能達到「遊戲成功」界面顯示在畫布上方了

<stack class="stack">
</stack>

在棧stack組件中增長一個遊戲成功的容器div類名爲subcontainer,以isShow控制該容器是否進棧,當isShow爲true時才進棧,增長文本組件text,類名gameover,並賦值「遊戲成功」

<div class="subcontainer" show="{ {isShow}}">
            <text class="gameover">
                遊戲成功
            </text>
        </div>

最後爲「從新開始」按鈕增長一個點擊事件click,所調用的函數爲restartGame

<input type="button" value="從新開始" class="bit" onclick="restartGame"/>

至此,index.hml文件已經所有編寫完畢

<div class="container" >
    <text class="seconds">
        當前秒數:{ { currentSeconds}}
    </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="bit" onclick="restartGame"/>
</div>

2.在index.css中描述剛纔添加的頁面組件的樣式:
首先編寫stack的樣式,width、height都設定爲320px,margin-top設定爲10px

.stack{ 
    width: 305px;
    height: 305px;
    margin-top: 10px;
}

而後編寫subcontainer的樣式,left爲指示距邊界框左上角的以像素爲單位的水平座標,top爲指示距邊界框左上角的以像素爲單位的垂直座標,width、height分別設定爲220px和130px,justify-content選擇center,align-items選擇center, background-color設定爲#E9C2A6

.subcontainer { 
    left:50px;
    top:95px;
    width: 220px;
    height: 130px;
    justify-content: center;
    align-items: center;
    background-color: #E9C2A6;
}

最後編寫gameover的樣式,font-size設定爲38px,color設定爲black

.gameover { 
    font-size: 38px;
    color: black;
}

至此,index.css文件已經所有編寫完畢
3.在index.js中描述頁面中的組件交互狀況:
首先在data函數中給isShow賦值爲false,將開頭的全局變量grids賦值刪除,增長一個函數onInit()給grids賦值,並調用函數initGrids()和this.drawGrids()

var grids;

data: { 
        currentSeconds:0,
        isShow: false
    },
    onInit() { 
        grids=[[1, 2, 3, 4],
               [5, 6, 7, 8],
               [9, 10, 11, 12],
               [13, 14, 15, 0]];

        this.initGrids();
        this.drawGrids();
    }

而後在開頭定義一個全局變量timer賦值爲null,在函數onShow()中增長一個計時器setInterval(),其中有兩個參數,第一個參數爲調用的函數,第二個參數爲每隔多長時間調用一次函數,單位爲毫秒,再定義調用的函數run(),用於每次currentSeconds加1,至此計時器便完成了

var timer = null;

onShow() { 
        this.initGrids();
        this.drawGrids();

        timer = setInterval(this.run, 1000);
    }
 
 run(){ 
        this.currentSeconds += 1;
    }

再在函數中swipeGrids(event)增長判斷數字是否有順序地排列好即判斷遊戲是否成功,循環判斷當前二維數組的數值是否等於有順序排列好的二維數組的數值便可判斷遊戲是否成功,當遊戲成功時將isShow賦值爲true,使遊戲成功界面顯示在最上方,而且調用函數clearInterval中止計時

swipeGrids(event) { 
        this.changeGrids(event.direction);
        this.drawGrids();

        let originalgrids=[[1, 2, 3, 4],
                           [5, 6, 7, 8],
                           [9, 10, 11, 12],
                           [13, 14, 15, 0]];
        let k = 1;
        for (let row = 0; row < 4; row++) { 
            for (let column = 0; column < 4; column++) { 
                if (grids[row][column] != originalgrids[row][column]){ 
                    k = 0;
                }
            }
        }
        if(k){ 
            clearInterval(timer);
            this.isShow = true;
        }
    }

最後建立點擊「從新開始」按鈕所自動調用的函數restartGame(),調用函數onInit(),從新隨機生成一個數字被隨意打亂的4*4的方陣,將isShow賦值爲false,使「遊戲成功」界面隱藏,將currentSeconds置爲0,將time賦值爲null,調用函數onShow()從新開始計時

restartGame(){ 
        this.onInit();
        this.isShow = false;
        timer = null;
        this.onShow();
        this.currentSeconds = 0;
    }

至此,index.jd文件已經所有編寫完畢,整個demo也所有完成了

心得體會

本個demo總體難度不高,涉及的組件或樣式都是很常見的,須要掌握棧、按鈕、畫布、計時器、滑動等組件便可完成編寫,組件交互採用二維數組串連整個項目,十分適合剛入門的讀者編寫與學習,其中組件學習能夠前往官方文檔查看更詳細的介紹:官方文檔,較之第一個遊戲黑白翻棋,組件雖多了一種,但算法更簡單了,代碼更優化,可讀性更增強,建議讀者在學習時能夠與第一個遊戲黑白翻棋對照一塊兒學習:黑白翻棋

結語

本次項目爲博主學習了鴻蒙一些基礎後自行編寫完成的,感興趣的讀者能夠自行跟着本博客編寫,相信大家也可以完成的。若是有遇到什麼問題,或者查找出其中的錯誤之處,歡迎留言,本博主也歡迎與各位感興趣的讀者一塊兒學習HarmonyOS(鴻蒙)開發。

源代碼

index.hml以下:

<div class="container" >
    <text class="seconds">
        當前秒數:{ { currentSeconds}}
    </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="bit" onclick="restartGame"/>
</div>

index.css以下:

.container { 
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width:450px;
    height:450px;
}
.seconds{ 
    font-size: 18px;
    text-align:center;
    width:300px;
    height:20px;
    letter-spacing:0px;
    margin-top:10px;
}
.canvas{ 
    width:305px;
    height:305px;
    background-color: #FFFFFF;
}
.bit{ 
    width:150px;
    height:30px;
    background-color:#AD9D8F;
    font-size:24px;
    margin-top:10px;
}
.stack{ 
    width: 305px;
    height: 305px;
    margin-top: 10px;
}
.subcontainer { 
    left:50px;
    top:95px;
    width: 220px;
    height: 130px;
    justify-content: center;
    align-items: center;
    background-color: #E9C2A6;
}
.gameover { 
    font-size: 38px;
    color: black;
}

index.js以下:

var grids;
var context;
var timer = null;

const SIDELEN = 70;
const MARGIN = 5;

export default { 
    data: { 
        currentSeconds:0,
        isShow: false
    },
    onInit() { 
        grids=[[1, 2, 3, 4],
               [5, 6, 7, 8],
               [9, 10, 11, 12],
               [13, 14, 15, 0]];

        this.initGrids();
        this.drawGrids();
    },
    initGrids(){ 
        let array=["left","up","right","down"];

        for (let i = 0; i < 100; i++){ 
            let randomIndex = Math.floor(Math.random() * 4);
            let direction = array[randomIndex];
            this.changeGrids(direction);
        }
    },
    onReady() { 
        context = this.$refs.canvas.getContext('2d');
    },
    onShow() { 
        this.initGrids();
        this.drawGrids();

        timer = setInterval(this.run, 1000);
    },
    drawGrids() { 
        for (let row = 0; row < 4; row++) { 
            for (let column = 0; column < 4; column++) { 
                let gridStr = grids[row][column].toString();

                context.fillStyle = "#BBADA0";
                let leftTopX = column * (MARGIN + SIDELEN) + MARGIN;
                let leftTopY = row * (MARGIN + SIDELEN) + MARGIN;
                context.fillRect(leftTopX, leftTopY, SIDELEN, SIDELEN);

                context.font = "30px HYQiHei-65S";
                if (gridStr != "0") { 
                    context.fillStyle = "#000000";
                    let offsetX = (4 - gridStr.length) * (SIDELEN / 8);
                    let offsetY = (SIDELEN - 30) / 2;
                    context.fillText(gridStr, leftTopX + offsetX, leftTopY + offsetY);
                }
            }
        }
    },
    run(){ 
        this.currentSeconds += 1;
    },
    swipeGrids(event) { 
        this.changeGrids(event.direction);
        this.drawGrids();

        let originalgrids=[[1, 2, 3, 4],
                           [5, 6, 7, 8],
                           [9, 10, 11, 12],
                           [13, 14, 15, 0]];
        let k = 1;
        for (let row = 0; row < 4; row++) { 
            for (let column = 0; column < 4; column++) { 
                if (grids[row][column] != originalgrids[row][column]){ 
                    k = 0;
                }
            }
        }
        if(k){ 
            clearInterval(timer);
            this.isShow = true;
        }
    },
    changeGrids(direction) { 
        let x;
        let y;
        for (let row = 0; row < 4; row++) { 
            for (let column = 0; column < 4; column++) { 
                if (grids[row][column] == 0) { 
                    x = row;
                    y = column;
                    break;
                }
            }
        }
        let temp;
        if (direction == 'left' && (y + 1) < 4) { 
            temp = grids[x][y + 1];
            grids[x][y + 1] = grids[x][y];
            grids[x][y] = temp;
        } else if (direction == 'right' && (y - 1) > -1) { 
            temp = grids[x][y - 1];
            grids[x][y - 1] = grids[x][y];
            grids[x][y] = temp;
        } else if (direction == 'up' && (x + 1) < 4) { 
            temp = grids[x + 1][y];
            grids[x + 1][y] = grids[x][y];
            grids[x][y] = temp;
        } else if (direction == 'down' && (x - 1) > -1) { 
            temp = grids[x - 1][y];
            grids[x - 1][y] = grids[x][y];
            grids[x][y] = temp;
        }
    },
    restartGame(){ 
        this.onInit();
        this.isShow = false;
        timer = null;
        this.onShow();
        this.currentSeconds = 0;
    }
}
相關文章
相關標籤/搜索