本個demo将從零基礎開始完成鴻蒙小遊戲APP在可穿戴設備上的編譯,此處以運動手表為例,在項目中我們所使用到的軟件為DevEco Studio,下載地址為:DevEco Studio下載、DevEco Studio安裝教程,在項目中我們要實現的内容為數字華容道APP的開發。
在初始界面中顯示4*4的方陣,方陣中分布有随意打亂的1至15的數字和一個空白方格,方陣上方增加一個計時器,顯示遊戲進行的時間,單位為秒,方陣下方顯示一個“重新開始”的按鈕,為用戶提供重新開始遊戲的機會。向上、下、左、右任一方向滑動,空白方格周圍對應位置的方格便會随之向對應的方向移動一格,計時器也會顯示遊戲開始到當前的時間。經過若幹次移動後,當所有的數字按順序排列後,則會彈出遊戲成功的界面,再滑動也不會有任何變化,此時方陣上方的計時器停止計時,點擊“重新開始”的按鈕後則會重新返回步驟1界面所示。正文創建項目 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,用于盛裝所有的其他組件
div class=container
然後在此基礎容器中添加一個文字組件text類名為seconds,且注明顯示的固定部分“當前秒數:”,為動态變換部分賦予一個名為currentSteps的變量,用于動态顯示遊戲進行的秒數
text class=seconds 當前秒數:{ { currentSeconds}} /text
再添加一個畫布組件canvas類名為canvas,增加一個引用屬性ref,用來指定指向子元素或子組件的引用信息,該引用将注冊到父組件的$refs 屬性對象上,以便在此畫布上畫出4*4的方陣
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
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(); }
再創建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 row ) { for (let column = 0; column 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 != ) { 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(); }, onShow() { this.drawGrids(); }, drawGrids() { for (let row = 0; row row ) { for (let column = 0; column 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 != ) { 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 row ) { for (let column = 0; column 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 == && (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=[leftuprightdown for (let i = 0; i 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
最後為“重新開始”按鈕增加一個點擊事件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 /stack input type=button value=重新開始 class=bit onclick=restartGame
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 row ) { for (let column = 0; column 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 /stack input type=button value=重新開始 class=bit onclick=restartGame
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=[leftuprightdown for (let i = 0; i i ){ let randomIndex = Math.floor(Math.random() * 4); let direction = array[randomIndex]; this.changeGrids(direction); } }, onReady() { context = this.$refs.canvas.getContext(); }, onShow() { this.initGrids(); this.drawGrids(); timer = setInterval(this.run, 1000); }, drawGrids() { for (let row = 0; row row ) { for (let column = 0; column 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 != ) { 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 row ) { for (let column = 0; column 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 row ) { for (let column = 0; column 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 == && (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; } }
内容來源于:htt