#2020征文-手表#深鸿会深大小组:HarmonyOS小游戏:贪吃蛇 原创 精华
前言
随着学习的深入,我们决定把经典游戏贪吃蛇移植到鸿蒙上,这篇文章用于记录学习中的感悟,也为了分享给对鸿蒙有兴趣的初学者们,希望在这个过程中能够相互交流、共同进步。
一同参与编写的还有:
张尧
mb5fa4e07864f6f
Linzijiandevx
WeChat15820482064
概述
使用工具:DevEco Studio 下载地址:[https://developer.harmonyos.com/cn/develop/deveco-studio]
要完成贪吃蛇游戏,我们需要完成的工作有:
1. 项目的创建
2. 完成基本布局
3. 把蛇、食物、地面绘制出来
4. 让蛇动起来
5. 吃到食物的判定
6. 游戏的重新开始与计分
项目的创建
打开DevEco Studio,然后在左上角的file上选择new->new project创建新项目,为项目命名(不能有中文)。
我们要做的工作主要是修改下图的.hml,.js,.css文件:
完成基本布局
下面是我们这步要实现的效果图
首先我们在hml文件中把组件组织起来:
把原有div组件中的text组件的文字改为当前分数,把该text的class值设为score类,并动态绑定一个变量score,接着设立一个新的组件stack,class类值设为stack,在这个stack组件中放置一个canvas组件,class值设为canvas,ref值设为canvas。
在stack组件后面再添加一个input组件,type值为button,class值设为bit,value设为“重新开始”。
<div class="container">
<text class="score">
当前分数: {{score}}
</text>
<stack class="stack">
<canvas class="canvas" ref="canvas" ></canvas>
</stack>
<input type="button" value="重新开始" class="bit" onclick="reStart" />
</div>
然后我们在css文件中把hml中的要应用的class值设定好。
.container {
flex-direction: column;
justify-content: center;
align-items: center;
width: 454px;
height: 454px;
}
.score {
font-size: 18px;
text-align:center;
width:300px;
height:20px;
letter-spacing:0px;
margin-button:5px;
}
.canvas{
align-items: center;
flex-direction: column;
width:320px;
height:320px;
background-color: #BBADA0;
}
.bit{
width:150px;
height:30px;
background-color:#AD9D8F;
font-size:24px;
margin-top:10px;
}
.stack{
width: 305px;
height: 305px;
margin-top: 10px;
}
在js文件中,先设定score的值为0:
export default {
data: {
score:0
}
}
然后点击previewer可以看到我们要的预览效果。
把蛇、食物、地面绘制出来
在这一步中我们主要是对js文件进行修改:
首先我们初始化一下我们的矩阵,并定义蛇的初始位置(此处也可以让蛇随机生成,方法不唯一),初始化SX,SY数组与矩阵对应,建立与数字对应的颜色字典。
我们利用getContext函数用以得到绘图组件。
首先给边长和边距赋值,其次定义函数draw()用于绘制所有格子,编译行、列索引,并将行列索引获得的数字转换为字符串,然后赋值给变量gridStr。最后通过字典获得对应格子的颜色并赋值给context.fillStyle,并调用context.fillRect来绘制矩形(左上角X,左上角Y,长,宽)
const SIDELEN = 25; //总体布局大小(边长)
const MARGIN = 5; //总体布局大小(边距)
draw() {
for (let i = 0;i < 10; i++) {
for (let j = 0;j < 10; j++) {
let gridStr = map[i][j].toString();
let leftTopX = j * (MARGIN + SIDELEN) + MARGIN;
let leftTopY = i * (MARGIN + SIDELEN) + MARGIN;
context.fillStyle = CORLOR[gridStr];
context.fillRect(leftTopX,leftTopY,SIDELEN,SIDELEN);
}
}
},
绘制蛇和食物
首先定义各类常数和颜色
var SX;//蛇
var SY;//蛇
var GX = 2; //苹果的位置
var GY = 2; //苹果的位置
const CORLOR = {
"0": "#AD9D8F", //地面
"1": "#EEE4DA", //蛇头
"2": "#EEE32A", //蛇身
"3": "#F67C5F" //食物
}
然后用二维数组设置蛇和食物的初始位置,其中SX和SY的第一个元素,即0和1,代表蛇头的位置;第二个元素,即0和0,代表蛇身的位置。
initMap() {
map = [[2, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
SX = [0, 0];
SY = [1, 0];
},
设置用于初始化的函数。
onInit()分别初始化分数、蛇和十五的位置、蛇行走的方向,并隐藏“游戏结束”的字样。
onReady()为生命周期事件,通过this.$refs.canvas来获得组件canvas的对象实例,然后调用getContext()函数传入实参2d来获得2d绘制引擎,然后赋值给变量context。由于context可能会在其他地方用到,故声明其为全局变量。
onShow()设置蛇前进的速度以及调用绘制蛇、食物、地面的函数。
总的代码如下:
var map //地图
var SX;//蛇
var SY;//蛇
var GX = 2; //苹果的位
var GY = 2; //苹果的位置
var context;
const SIDELEN = 25; //总体布局大小
const MARGIN = 5; //总体布局大小
const CORLOR = {
"0": "#AD9D8F", //地面
"1": "#EEE4DA", //蛇头
"2": "#EEE32A", //蛇身
"3": "#F67C5F" //食物
}
export default {
data: {
score: 0,
isshow: false
},
onInit() {//代表初始化
this.score=0;
this.initMap();
Xstep = 0;
Ystep = 1;
GX = 2;
GY = 2;
clearInterval(timer);
this.isshow=false;
},
onReady() {//程序的准备
context = this.$refs.canvas.getContext('2d');
},
onShow() {//游戏界面的展示
timer = setInterval(this.run,500);
this.draw();
},
initMap() {
map = [[2, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 3, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
SX = [0, 0];
SY = [1, 0];
},
draw() {
for (let i = 0;i < 10; i++) {
for (let j = 0;j < 10; j++) {
let gridStr = map[i][j].toString();
let leftTopX = j * (MARGIN + SIDELEN) + MARGIN;
let leftTopY = i * (MARGIN + SIDELEN) + MARGIN;
context.fillStyle = CORLOR[gridStr];
context.fillRect(leftTopX,leftTopY,SIDELEN,SIDELEN);
}
}
},
效果图如下:
让蛇动起来
让蛇移动起来大概分为如下两步,其中第一步,接收具体的移动指令,第二步,则是根据指令让蛇开始行动。
首先,我们定义函数changeDirection,把事件方向进Xstep与Ystep两个变量之中,比如向右储存为x方向行进1步,y方向行进0步,其他同理。
changeDirection(event) {
if (event.direction == "right" ) {
Xstep = 1;
Ystep = 0;
}
if (event.direction == "left" ) {
Xstep = -1;
Ystep = 0
}
if (event.direction == "up" ) {
Xstep = 0;
Ystep = -1;
}
if (event.direction == "down" ) {
Xstep = 0;
Ystep = 1;
}
},
然后我们在hml文件中的canvas组件中把onswipe值设为changeDirection,让这个函数接收参数。(不能忘记修改hml文件,否则你在js文件上的函数就没有被调用)
<div class="container">
<text class="score">
当前分数: {{score}}
</text>
<stack class="stack">
<canvas class="canvas" ref="canvas" onswipe="changeDirection"></canvas>
</stack>
<input type="button" value="重新开始" class="bit" onclick="reStart" />
</div>
接着我们处理蛇的前进,首先是蛇身的前进,
蛇身的每一块移动到它前一格所在的位置。
for (let i = SX.length - 1; i > 0; i--) {
SX[i] = SX[i - 1];
SY[i] = SY[i - 1];
}
接着是蛇头的移动,我们导入方向,移动蛇头。
SX[0] += Xstep;
SY[0] += Ystep;
map[SY[0]][SX[0]] = 1;
map[GY][GX] = 3;
吃到食物的判定
吃到食物的判定这一步首先先判定蛇吃到食物的同时有没有吃到自己的身体,以免出现BUG,吃到自己身体的判定如下:
eatSelf(){
for(let i=1;i<SX.length;i++){
if(SX[0]==SX[i]&&SY[0]==SY[i]){
clearInterval(timer);
this.isshow = true;
return true;
}
}
return false;
},
然后判断蛇头是否出界。
OutorNot() {
//判断是否出界
if (SX[0] < 0 || SX[0] > 9 || SY[0] < 0 || SY[0] > 9) {
clearInterval(timer);
this.isshow = true;
return false;
}
return true;
},
然后判断蛇的头是否和果实重合若重合就添加数组来增长蛇长。
if(!this.eatSelf()){
if(SX[0]==GX&&SY[0]==GY){
SX.push(GX);
SY.push(GX);
}
之后就是重新更新新的食物,为了不让食物在蛇的身体上,遍历整部地图,找出为地图上等于0的位置,把他的X,Y坐标放进数组中,然后用这个数组里随机选出一组X,Y坐标出来,添加在那个位置`。
let array=[];
for(let i=0;i<10;i++){
for(let j=0;j<10;j++){
if(map[i][j]==0){
array.push([i,j]);
}
}
}
let XandY = array[Math.floor(Math.random()*array.length)];
//math.floor()函数是向下取整的意思,Math.random()是随机生成0到1之间的数,而array.length用于得到数组的长度,
//这样我们就可以随机得到某块地板的横纵坐标并将其设为食物了。
GX=XandY[0];
GY=XandY[1];
结合刚刚的函数,我们编写出run()函数:
run() {
this.changeDirection;
if (this.OutorNot()) {
map = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]];
//改变SX,SY数组
for (let i = SX.length - 1; i > 0; i--) {
SX[i] = SX[i - 1];
SY[i] = SY[i - 1];
}
for (let i = 1;i < SX.length; i++) {
map[SY[i]][SX[i]] = 2;
}
//让蛇头前进
SX[0] += Xstep;
SY[0] += Ystep;
map[SY[0]][SX[0]] = 1;
map[GY][GX] = 3;
//在空白的地方随机生成食物
if(!this.eatSelf()){
if(SX[0]==GX&&SY[0]==GY){
SX.push(GX);
SY.push(GX);
let array=[];
for(let i=0;i<10;i++){
for(let j=0;j<10;j++){
if(map[i][j]==0){
array.push([i,j]);
}
}
}
let XandY = array[Math.floor(Math.random()*array.length)];
this.updateScore();
GX=XandY[0];
GY=XandY[1];
}
this.draw();
}
}
},
游戏的重新开始与计分
游戏的重新开始:
首先我们在data中添加一个变量isshow,便于我们添加游戏失败的字样,当isshow的值为false时,该界面不出现,当isshow的值为ture时,则出现。
然后我们在hml中stack组件中加入一个div组件,class赋值为subcantainer,show赋值为isshow,在该容器中加入text组件,添加字样游戏结束。
对css:
<div class="subcontainer" show="{{isshow}}">
<text class="gameover">
游戏结束
</text>
</div>
对js:
onInit() {
this.score=0;
this.initMap();
Xstep = 0;
Ystep = 1;
GX = 2;
GY = 2;
clearInterval(timer);
this.isshow=false;
},
onReady() {
context = this.$refs.canvas.getContext('2d');
},
onShow() {
timer = setInterval(this.run,500);
this.draw();
},
计分
定义更新分数的函数,每次加1分
updateScore(){
this.score++;
}
并且插入到更新食物的函数中,每次更新食物就调用一次
if(!this.eatSelf()){
if(SX[0]==GX&&SY[0]==GY){
SX.push(GX);
SY.push(GX);
let array=[];
for(let i=0;i<10;i++){
for(let j=0;j<10;j++){
if(map[i][j]==0){
array.push([i,j]);
}
}
}
let XandY = array[Math.floor(Math.random()*array.length)];
this.updateScore();
GX=XandY[0];
GY=XandY[1];
}
this.draw();
}
结语
以上就是小游戏贪吃蛇代码的主要编写思路以及代码,源码将放在附件中,欢迎大家下载,也欢迎评论区留言讨论,让我们一起进步!也欢迎大家参加我们在极客创建的项目“深鸿会 / Awesome-HarmonyOS_木棉花”https://gitee.com/hiharmonica/awesome-harmony-os-kapok一起为鸿蒙的发展贡献自己的一份力量!
如果是本社区原创贴,这篇文章的质量,可以参加征文哦。
这文章写的真详细,感谢楼主分享,写贴辛苦了
已投稿 谢谢提醒
在标题开头添加“#2020征文-手表#,才能视为参加比赛哦。详细规则楼主可以参考下面这个链接的文章https://harmonyos.51cto.com/posts/1940
谢谢鼓励!