作者 : 郝志鹏
前言
从零开始学习HarmonyOS开发相关知识,对于从没接触过的FA感到无从下手。单纯看文档,练习文档中的组件提升不大,突发奇想决定结合ArkUI(JS)和canvas实现一个简单的推箱子小游戏,来深入学习FA的实际应用。
实现效果

游戏说明
1. 通过操作上、下、左、右四个按钮移动皮卡丘;
2. 当皮卡丘对应的运动方向为墙壁时不可移动;
3. 当皮卡丘对应的运动方向为一个箱子时,皮卡丘和箱子同时向对应方向移动一个单位;
4. 当皮卡丘对应运动方向相邻的箱子后为另一个箱子或墙壁时,皮卡丘和箱子皆无法移动;
5. 点击上一步可回退到上一步操作;
6. 点击重置按钮可回退到初始位置;
7. 当所有箱子都移动到圆形阴影所在位置,即可通关游戏;
项目说明
主要用到知识点:canvas, showToast
进行图像绘制
drawImage(image: Image, sx: number, sy: number, sWidth: number, sHeight: number, dx: number, dy: number, dWidth: number, dHeight: number):void
参数 |
类型 |
描述 |
image |
Image |
图片资源,请参考Image对象。 |
sx |
number |
裁切源图像时距离源图像左上角的x坐标值。 |
sy |
number |
裁切源图像时距离源图像左上角的y坐标值。 |
sWidth |
number |
裁切源图像时需要裁切的宽度。 |
sHeight |
number |
裁切源图像时需要裁切的高度。 |
dx |
number |
绘制区域左上角在x轴的位置。 |
dy |
number |
绘制区域左上角在y 轴的位置。 |
dWidth |
number |
绘制区域的宽度。 |
dHeight |
number |
绘制区域的高度。 |
删除指定区域内的绘制内容
clearRect(x: number, y: number, width:number, height: number): void
参数 |
类型 |
描述 |
x |
number |
指定矩形上的左上角x坐标。 |
y |
number |
指定矩形上的左上角y坐标。 |
width |
number |
指定矩形的宽度。 |
height |
number |
指定矩形的高度。 |
实现步骤
1. 以画布左上角作为原点坐标(0, 0);
2. 根据墙壁对应数组的坐标绘制墙壁,同理依次绘制圆形阴影、箱子、皮卡丘的位置;
3. 上、下、左、右四个按钮绑定对应点击事件,用来移动皮卡丘;
4. 当移动皮卡丘时进行碰撞检测的判断(是否碰到墙壁、是否碰到箱子、是否可以推动箱子、箱子的坐标位置是否与圆形阴影重合);
5. 每次移动箱子判断,是否所有箱子都与圆形阴影重合,如果全部重合则代表游戏通关,弹出toast提示;
代码实现
1. hml部分
2. css部分
3. js部分
import prompt from '@system.prompt';
export default {
data: {
walls: [
[0, 0], [1, 0], [2, 0], [3, 0], [4, 0],
[0, 1], [4, 1],
[0, 2], [2, 2], [4, 2],
[0, 3], [4, 3],
[0, 4], [2, 4], [4, 4], [5, 4], [6, 4],
[0, 5], [6, 5],
[0, 6], [6, 6],
[0, 7], [1, 7], [2, 7], [3, 7], [4, 7], [5, 7], [6, 7]
],
boxPos: [[3, 2], [3, 5], [4, 6]],
overPos: [[1, 1], [1, 5], [3, 6]],
personPos: [3, 6],
drawCanvas: '',
boxHistory: ['[[3, 2], [3, 5], [4, 6]]'],
personHistory: ['[3, 6]'],
winGame: false,
},
onShow(){
this.initCanvas()
},
initCanvas() {
let canvas = this.$element('canvas')
if(!canvas) return
this.drawCanvas = canvas.getContext('2d', { antialias: true })
this.initWalls()
this.initOverPos()
this.initBoxPos()
this.initPersonPos()
},
initWalls() {
let img = new Image();
img.src = 'common/images/game/wall.jpg';
this.walls.forEach(item => {
this.drawCanvas.drawImage(img, item[0] * 50, item[1] * 50, 50, 50);
})
},
initBoxPos() {
let self = this
let img = new Image();
img.src = 'common/images/game/box.png';
let overImg = new Image();
overImg.src = 'common/images/game/over_box.png';
this.boxPos.forEach(item => {
this.drawCanvas.drawImage(self.boxOver(item) ? overImg : img , item[0] * 50, item[1] * 50, 50, 50);
})
},
initOverPos() {
let img = new Image();
img.src = 'common/images/game/over.png';
this.overPos.forEach(item => {
this.drawCanvas.drawImage(img, item[0] * 50, item[1] * 50, 50, 50);
})
},
initPersonPos() {
let img = new Image();
img.src = 'common/images/game/player.png';
this.drawCanvas.drawImage(img, this.personPos[0] * 50, this.personPos[1] * 50, 50, 50);
},
handleUp() {
this.hanndleMove(1, -1)
},
handleLeft() {
this.hanndleMove(0, -1)
},
handleRight() {
this.hanndleMove(0, 1)
},
handleDown() {
this.hanndleMove(1, 1)
},
hanndleMove(posIndex, step) {
if (this.winGame) return
let oldPersonPos = JSON.parse(JSON.stringify(this.personPos))
this.personPos[posIndex] = oldPersonPos[posIndex] + step
if(this.collisionDetectionWall(this.personPos)) {
this.personPos = oldPersonPos
return
}
let boxIndex = this.collisionDetectionBox(this.personPos)
if(boxIndex > -1) {
let oldboxPos = JSON.parse(JSON.stringify(this.boxPos[boxIndex]));
this.boxPos[boxIndex][posIndex] = oldboxPos[posIndex] + step;
if (this.collisionDetectionWall(this.boxPos[boxIndex]) || this.collisionDetectionBoxToBox(boxIndex)) {
this.personPos = oldPersonPos;
this.boxPos[boxIndex] = oldboxPos;
return;
}
this.isWin()
}
this.addHistroy()
this.drawCanvas.clearRect(0, 0, 500, 500);
this.initCanvas()
},
collisionDetectionWall(pos) {
for (let i = 0, len = this.walls.length; i < len; i++) {
if (this.walls[i][0] == pos[0] && this.walls[i][1] == pos[1]) {
return true;
}
}
return false;
},
collisionDetectionBox(pos) {
for (let i = 0, len = this.boxPos.length; i < len; i++) {
if (this.boxPos[i][0] == pos[0] && this.boxPos[i][1] == pos[1]) {
return i;
}
}
return -1;
},
collisionDetectionBoxToBox(index) {
for (let i = 0, len = this.boxPos.length; i < len; i++) {
if (i == index) continue
if (this.boxPos[i][0] == this.boxPos[index][0] && this.boxPos[i][1] == this.boxPos[index][1]) {
return true;
}
}
return false;
},
boxOver(pos) {
for (let i = 0, len = this.overPos.length; i < len; i++) {
if (this.overPos[i][0] == pos[0] && this.overPos[i][1] == pos[1]) {
return true;
}
}
return false;
},
addHistroy() {
this.boxHistory.push(JSON.stringify(this.boxPos))
this.personHistory.push(JSON.stringify(this.personPos))
},
handleBack() {
if (this.winGame) return
if(this.boxHistory.length > 0 && this.personHistory.length > 0) {
this.boxHistory.pop()
this.personHistory.pop()
this.boxPos = JSON.parse(this.boxHistory[this.boxHistory.length - 1])
this.personPos = JSON.parse(this.personHistory[this.personHistory.length - 1])
this.drawCanvas.clearRect(0, 0, 500, 500);
this.initCanvas()
}
},
handleReset() {
this.winGame = false
this.boxHistory = [].concat(this.boxHistory[0])
this.personHistory = [].concat(this.personHistory[0])
this.boxPos = JSON.parse(this.boxHistory[0])
this.personPos = JSON.parse(this.personHistory[0])
this.drawCanvas.clearRect(0, 0, 500, 500);
this.initCanvas()
},
isWin() {
let count = 0;
this.overPos.forEach((item) => {
this.boxPos.forEach(box => {
if (box[0] == item[0] && box[1] == item[1]) {
count++;
}
})
});
if (count == this.boxPos.length) {
this.winGame = true
this.showToast()
}
},
showToast() {
prompt.showToast({
message: '恭喜你,通关了!',
duration: 5000,
bottom: 500,
});
},
};
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
总结
对于FA目前还在继续学习的路上,这个小游戏写的比较简单,还有很多不足之处,大家有什么想法和意见可以提出来,共同进步,谢谢。
注:图片素材见附件
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
看文档确实不如做一个小游戏能更好的学到知识。
自己动手写东西印象会更深刻吧,学到东西印象就更深一点
都懂,就想知道是不是直接上了最难的一关