[深大深鸿会]利用DevEco Studio从零开发OpenHarmony小游戏——2048(上)
从零开发鸿蒙小游戏——2048(上)
前言
最近与小伙伴一起跟着张荣超老师的视频课程《从零开发鸿蒙小游戏App》学习了许久,受益匪浅。为了更好的掌握所学的知识,我们在这里写下这篇文章,用于记录学习过程中理解与感悟,也分享给更多和我一样的鸿蒙初学者,希望在这个过程中能够相互交流、共同进步。本文也是我们第一次写的博客,如果在内容上或排版上有什么好的建议,欢迎向我提出来。
共同学习的小伙伴:
xxl_connorxian
RichardCwy
Les24601_
JE13543733623
yeswin411
概述
本次课程将从零开始实现一个运行在鸿蒙设备上的经典小游戏2048。本文所实现的功能有:页面布局、显示格子与数字、游戏页面的初始化。后续的功能会写在下一篇文章中。
- 课程使用的IDE为华为的DevEco Studio
- 课程开发的应用将运行在虚拟设备Huawei Lite Wearable上
- 课程希望实现的app为小游戏2048
- 课程所使用的语言为js
2048游戏规则简要介绍
- 开始游戏时随机两个格子上会出现2或4
- 上下左右滑动进行操作,画面中的数字会朝相应方向滑动,相同的数字滑到一起则数字会相加,然后随机在一个空格子上生成2或4
- 若格子全满且无法再通过滑动合并格子则游戏结束
创建项目
创建新的项目文件:点击左上角file,点击new,选择Lite Wearable选项,选择默认的模板(如图),然后将文件命名为Game2048(注意,文件名的路径中不要出现中文,否则无法创建新项目)。
创建完成后,可以看见左侧的资源管理器中有有如下文件:
我们需要编程的地方主要在index.css、index.hml、index.js这三个文件上。其中css负责确定组件的样式(Presentation),hml决定页面的结构(Structure),js负责控制组件的行为( Behavior)。
项目的实现
页面布局
首先我们希望实现如下布局
其中最高分与当前分的显示采用动态绑定的方式,在index.js中设置初始值9818与0。
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>
样式设置:
.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文件中定义一个二维数组,用于存放各格子内的数字
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>
结语
到这里就是张荣超老师课程目前已实现的内容了,接下来还未实现的功能有滑动格子和格子的合并、分数的计算以及游戏结束的判断。我会继续对张老师给出的源代码进行学习与解读学习其他功能的实现,并将学习的内容写在下一篇文章中。