[深大深鸿会]利用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>
结语
到这里就是张荣超老师课程目前已实现的内容了,接下来还未实现的功能有滑动格子和格子的合并、分数的计算以及游戏结束的判断。我会继续对张老师给出的源代码进行学习与解读学习其他功能的实现,并将学习的内容写在下一篇文章中。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
有了 HTTP 协议,为什么还要 RPC 协议,两者有什么区别?
很长时间以来都没有搞懂 RPC(即 Remote Procedure Call,远程过程调用)和 HTTP 调用的区别,不都是写一个服务然后在客户端调用么?这里请允许我迷之一笑~Naive! 本文简单地介绍一下两种形式的 C/S 架构,先说一下他们最本质的区别,就是 RPC 主要是基于 TCP/IP 协议的,而 HTTP 服务主要是基于 HTTP 协议的。 我们都知道 HTTP 协议是在传输层协议 TCP 之上的,所以效率来看的话,RPC 当然是要更胜一筹啦!下面来具体说一说 RPC 服务和 HTTP 服务。 OSI 网络七层模型 在说 RPC 和 HTTP 的区别之前,我觉的有必要了解一下 OSI 的七层网络结构模型(虽然实际应用中基本上都是五层)。 它可以分为以下几层:(从上到下) 第一层:应用层。定义了用于在网络中进行通信和传输数据的接口。 第二层:表示层。定义不同的系统中数据的传输格式,编码和解码规范等。 第三层:会话层。管理用户的会话,控制用户间逻辑连接的建立和中断。 第四层:传输层。管理着网络中的端到端的数据传输。 第五层:网络层。定义网络设备间如何传输数据。 第六层:链路...
- 下一篇
Go 大数据生态迎来重要产品 CDS
项目地址: https://github.com/tal-tech/cds https://gitee.com/zeromicro/cds ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。它有着优异的性能,可以快速部署和运行。 不过要想使用ClickHouse搭建起数仓用于数据分析,一个重要的问题就是数据如何进入ClickHouse? 我们希望数据源的变化能够自动实时地被同步到ClickHouse,而且支持方便的动态的添加新的数据源(新的数据库,表), 能够自动的生成对应数据源的schema。 go-zero团队使用go语言围绕ClickHouse开发了一些方便的组件与服务。 我们得到了下面这样的数据同步设计 该数据同步系统大致由以下三部分组成 DM 全量同步服务 github.com/tal-tech/cds/dm RTU 实时增量同步服务 github.com/tal-tech/cds/rtu Galaxy 网页控制台服务 github.com/tal-tech/cds/galaxy 流程如下: 用户可以在网页控制台添加数据源,自动生成DDL,添...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8安装Docker,最新的服务器搭配容器使用
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程