前言
管道小鸟是一款比较经典的小游戏,基本操作就是用户点击屏幕让小鸟蹦跶,然后穿过一个一个下水管道,每穿过一个,则会增加指定分数,越往后速度越快。好玩是好玩,就是有点费手。
下面就开始游戏编写。
注: 游戏里数值都是固定的,比如鸟的大小,管道的距离,以及他们上下左右距离,是为了方便进行距离与距离之间的计算。比如计算鸟是否撞到顶部、撞到地板、撞到管道等。
又因为此游戏之前使用uniapp写的,uniapp用的是rpx,我也全部换成了px,导致部分一些数值计算的误差,喜欢的可在文章尾部取完整代码,自行调整。
一、基本布局
整体可以分为,背景、分数、开始游戏按钮、小鸟、管道、地板、地板彩带
<div class="bird-game">
<div class="bird-box">
<img src="img/bird-bg.png" class="bg" />
<div class="bird-map">
<!-- 分数 -->
<div class="bird-number">{{ number }}</div>
<!-- 开始游戏 -->
<div class="bird-start" v-if="!isStart">开始游戏</div>
<!-- 鸟 -->
<div
class="bird hidden"
:class="{'bird-move': !isStart}"
:style="{'top': bird.top + 'px', 'left': bird.left + 'px'}"
>
<img src="img/bird.gif" mode="widthFix" />
</div>
<!-- 管道 -->
<div class="bird-pipe" v-for="(item,index) in pipeList" :key="index" :style="{'right': item.pRight + 'px', 'width': pipe.width+'px'}">
<div class="bird-pipe-top" :style="{'top': item.pTop +'px'}">
<img src="img/pipe_t.png" width="100%" mode="widthFix" />
</div>
<div class="bird-pipe-bottom" :style="{'bottom': item.pBottom + 'px'}">
<img src="img/pipe_b.png" width="100%" mode="widthFix" />
</div>
</div>
</div>
</div>
<!-- 地板 -->
<div class="bird-land">
<div class="land-1"></div>
<div class="land-2"></div>
<div class="land-box land-move"></div>
<div class="land-3"></div>
<div class="land-4"></div>
</div>
</div>
如何让管道从右向左移动,看起来鸟像往前飞的感觉。这里就用到强大的css
首先需要画一个彩带
.land-box {
width: 100%;
height: 20px;
background: linear-gradient(-45deg, #9BE557 25%, #73BF2E 0%, #73BF2E 50%, #9BE557 0%, #9BE557 75%, #73BF2E 0%);
background-size: 40px 40px;
background-position: 250% 0;
}
然后加上animation即可
.land-move {
animation: ani_land 8.5s linear infinite;
}
@keyframes ani_land {
0% {background-position: 250% 0;}
100% {background-position: 0 0;}
}
二、游戏实现
(1)需要的配置参数
isStart: false, // 游戏是否开始
isEnd: false, // 游戏是否结束
number: 0, // 分数
map:{ width: 438, height: 600 }, // 地图
bird:{ // 小鸟
width: 34,
height: 24,
left: 80,
top: 300,
speedStart: 1, // 初始速度
speedPlus: 0.03, // 加速度
speedMax: 4, // 上限速度
flyMaxHeight: 50, // 每点一次飞行的最大高度
flyStageHeight: 5, // 飞行的阶梯增加高度
flyRelaHeight: 0, // 当前飞行的相对高度,也就是飞行的那个过程,高度是多少,因为有时没到达顶部,玩家又点一次
flyDirect: null, // up上升,down下落
},
flyTimer: null,
pipe:{ // 管道
width: 45,
height: 364,
},
pipeVerticel: 100, // 上下管道之间的垂直距离
pipeDistance: 200, // 左右管道之间的水平距离
pipeList: [ // 存放管道数组,默认先产生一个
{ pRight: -150, pTop: -200, pBottom: -200 }
],
(2)监听用户点击空格(用来开始游戏,起飞小鸟)
document.addEventListener('keydown', (e) => {
if(e.keyCode == 32) {
this.start()
}
})
(3)游戏开始
start(){
if(this.isEnd){
console.log('游戏已结束');
return;
}
if(this.flyTimer){
clearInterval(this.flyTimer);
this.flyTimer = null;
}
this.isStart = true;
this.bird.flyDirect = 'up';
this.flyTimer = setInterval(()=>{
this.birdFly();
this.pipeMove();
}, 10)
},
(4)小鸟运动
当我们每次点击空格的时候,小鸟都会向上运动,升到指定最高处,然后自由落体。
我们每次点击空格,其实就是改变小鸟的方向
birdFly(){
// 上升
if(this.bird.flyDirect == 'up'){
if(this.bird.flyRelaHeight <= this.bird.flyMaxHeight){
this.bird.top -= this.bird.flyStageHeight;
this.bird.flyRelaHeight += this.bird.flyStageHeight;
}else{
this.bird.flyDirect = 'down';
this.bird.flyRelaHeight = 0;
this.bird.speedStart = 1;
}
}
// 到达顶部
if(this.bird.top <= 0){
this.bird.top = 0;
this.die();
}
// 下降
if(this.bird.flyDirect == 'down'){
this.bird.top = this.bird.top + this.bird.speedStart * this.bird.speedStart;
if(this.bird.speedStart < this.bird.speedMax){
this.bird.speedStart += this.bird.speedPlus;
}
}
// 到达底边
if(this.bird.top >= this.map.height - this.bird.height){
this.bird.top = this.map.height - this.bird.height;
this.die();
}
}
小鸟上升是个减速运动,越来越慢,直到速度为0。然后再根据加速度,越来越快的加速下落。
而我们如果点击过快,小鸟一直上升,就可能会碰到顶部。
如果松手,就会一直下落,撞地板,都会游戏结束。
(5)管道移动
为啥需要让管道移动呢,其实这个游戏看起来是小鸟一直往前飞,其实小鸟的左右距离是固定不动的,只能上下移动,而真正的移动,是管道加上地板动画,让游戏看起来是一直向前的。所以这里我们就开始生成管道,然后让管道由右向左移动。
鸟的宽高是固定的,每个管道的高度是固定的,管道与管道之间上下左右的距离也都是能拿到的。
所以做的过程中我们只要多注意鸟与周围管道直接的距离把控,即可
// 管道移动 添加删除管道
pipeMove(){
// 如果数组中的最后一个到达右边的时候,增加一个
if(this.pipeList[this.pipeList.length - 1].pRight >= 0){
// 因为固定了上下管道距离,所以我们只要随机上下其中一个管道的距离值,再用整体高度-上下管道的距离 - 随机距离 = 另外一个管道的距离
let pipeRandom = this.random(50, 300);
let obj = {
pRight: -(this.pipeList[this.pipeList.length - 1].pRight*1 + this.pipeDistance*1),
pTop: -pipeRandom,
pBottom: -(this.map.height*1 - pipeRandom*1 - this.pipeVerticel*1),
};
this.pipeList.push(obj);
}
// 如果第一个到达左边的时候,删除第一个
if(this.pipeList[0].pRight >= this.map.width){
this.pipeList.shift();
}
// 遍历管道移动
for(let i = 0; i < this.pipeList.length; i++){
let birdMouthRight = this.map.width - this.bird.width - this.bird.left; // 鸟嘴到右边的距离
// 判断是否碰撞管道 (注意:已经飞过的管道需要去除)
// 当没有飞过当前的管道
if(this.pipeList[i].pRight <= this.map.width - this.bird.left){
// 有咩有撞到上管道
if(
(this.pipeList[i].pRight + this.pipe.width) >= birdMouthRight &&
(this.pipe.height + this.pipeList[i].pTop) >= this.bird.top
){
this.die();
return;
}
// 有咩有撞到下管道
if(
(this.pipeList[i].pRight + this.pipe.width) >= birdMouthRight &&
(this.pipe.height + this.pipeList[i].pBottom) >= (this.map.height - this.bird.height - this.bird.top)
){
this.die();
return;
}
}
// 判断分数,也就是刚好飞过某一个管道
console.log(this.pipeList[i].pRight);
const pRight = Math.floor(this.pipeList[i].pRight);
// 这个固定的数值不准确,
// 需要进行固定管道的右侧移动距离,使所有每次飞过的管道的pRight值都相等即可
if(pRight === 358){
this.number++;
}
this.pipeList[i].pRight += 1.2;
}
}
(6)游戏结束
die(){
clearInterval(this.flyTimer)
this.isEnd = true;
console.log('你死了');
},
三、总结
游戏整体不算难,但是注意的细节还是比较多的,尤其是对数值的计算。其中小鸟的上升与下降,还是不丝滑,此地方可优化,让鸟缓慢上升与重力下降。还有鸟的运动抬头与俯冲,都可以根据上升下降来进行调整。总的来说,游戏还是比较有瑕疵的,因为每个地方都是以前自己慢慢琢磨出来的,自我感觉还是良好的。
游戏吗,就是不断的摸索和优化。
最后附上完整代码
喜欢游戏的还可以看看我的其他游戏文章:
原文链接:https://juejin.cn/post/7218163893915189306 作者:大炮前端事务所