从零开发鸿蒙小游戏——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老师的教导。愿共勉之。