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