是的,又是我,在折腾小游戏的路上流连忘返了,之前用vue+canvas实现了一系列简单的小游戏,感兴趣的小伙伴可以戳戳以往的几个帖子:
今天就来实现一个雷霆战机打字游戏,玩法很简单,每一个“敌人”都是一些英文单词,键盘正确打出单词的字母,飞机就会发射一个个子弹消灭“敌人”,每次需要击毙当前“敌人”后才能击毙下一个,一个比手速和单词熟练度的游戏。
首先来看看最终效果图:
emmmmmmmmmmmmm,界面UI做的很简单,先实现基本功能,再考虑高大上的UI吧。
首先依旧是来分析界面组成:
(1)固定在画面底部中间的飞机;
(2)从画面上方随机产生的敌人(单词);
(3)从飞机头部发射出去,直奔敌人而去的子弹;
(4)游戏结束后的分数显示。
这次的游戏和之前的比,运动的部分貌似更多且更复杂了。在flappy bird中,虽然管道是运动的,但是小鸟的x坐标和管道的间隔、宽度始终不变,比较容易计算边界;在弹球消砖块游戏中,木板和砖块都是相对简单或者固定的坐标,只用判定弹球的边界和砖块的触碰面积就行。在雷霆战机消单词游戏中,无论是降落的目标单词,还是飞出去的子弹,都有着各自的运动轨迹,但是子弹又要追寻着目标而去,所以存在着一个实时计算轨道的操作。
万丈高楼平地起,说了那么多,那就从最简单的开始着手吧!
1、固定在画面底部中间的飞机
这个很简单没啥好说的,这里默认飞机宽度高度为40像素单位,然后将飞机画在画面的底部中间:
drawPlane() {
let _this = this;
_this.ctx.save();
_this.ctx.drawImage(
_this.planeImg,
_this.clientWidth / 2 - 20,
_this.clientHeight - 20 - 40,
40,
40
);
_this.ctx.restore();
},
2、从画面上方随机产生的敌人
这里默认设置每次在画面中最多只出现3个单词靶子,靶子的y轴移动速度为1.3,靶子的半径大小为10:
const _MAX_TARGET = 3; // 画面中一次最多出现的目标
const _TARGET_CONFIG = {
// 靶子的固定参数
speed: 1.3,
radius: 10
};
然后我们一开始要随机在词库数组里取出_MAX_TARGET个不重复的单词,并把剩下的词放进循环词库this.wordsPool中去:
generateWord(number) {
// 从池子里随机挑选一个词,不与已显示的词重复
let arr = [];
for (let i = 0; i < number; i++) {
let random = Math.floor(Math.random() * this.wordsPool.length);
arr.push(this.wordsPool[random]);
this.wordsPool.splice(random, 1);
}
return arr;
},
generateTarget() {
// 随机生成目标
let _this = this;
let length = _this.targetArr.length;
if (length < _MAX_TARGET) {
let txtArr = _this.generateWord(_MAX_TARGET - length);
for (let i = 0; i < _MAX_TARGET - length; i++) {
_this.targetArr.push({
x: _this.getRandomInt(
_TARGET_CONFIG.radius,
_this.clientWidth - _TARGET_CONFIG.radius
),
y: _TARGET_CONFIG.radius * 2,
txt: txtArr[i],
typeIndex: -1,
hitIndex: -1,
dx: (_TARGET_CONFIG.speed * Math.random().toFixed(1)) / 2,
dy: _TARGET_CONFIG.speed * Math.random().toFixed(1),
rotate: 0
});
}
}
}
可以看出,this.targetArr是存放目标对象的数组:
一个初始的目标有随机分布在画面宽度的x;
y轴值为直径;
txt记录靶子代表的单词;
typeIndex记录“轰炸”这个单词时正在敲击的字符索引下标(用来分离已敲击字符和未敲击字符);
hitIndex记录“轰炸”这个单词时子弹轰炸的索引下标(因为子弹真正轰炸掉目标是在打完单词之后,毕竟子弹有飞行时间,所以通过hitIndex来判断子弹什么时候被击碎消失);
dx是靶子每帧在x轴偏移距离;
dy是靶子每帧在y轴偏移距离;
rotate设置靶子自转角度。
好了,生成了3个靶子,我们就让靶子先从上往下动起来吧:
drawTarget() {
// 逐帧画目标
let _this = this;
_this.targetArr.forEach((item, index) => {
_this.ctx.save();
_this.ctx.translate(item.x, item.y); //设置旋转的中心点
_this.ctx.beginPath();
_this.ctx.font = "14px Arial";
if (
index === _this.currentIndex ||
item.typeIndex === item.txt.length - 1
) {
_this.drawText(
item.txt.substring(0, item.typeIndex + 1),
-item.txt.length * 3,
_TARGET_CONFIG.radius * 2,
"gray"