1.游戏展示
9最终成果展示
2.实现步骤
像素鸟游戏实现的思路
1. 绘制页面
<div id="land">
<div id="sky">
<div id="bird"></div>
<div id="start">
<img src="../bird-game/ready.png" alt="" id="ready">
<img src="../bird-game/tap.png" alt="" id="tap">
<p>键盘上下键切换颜色</p>
</div>
<div id="end">
<img src="../bird-game/gameover.png" alt="" id="over">
</div>
<div id="pause">
<img src="../bird-game/button_play.png" alt="">
</div>
<div id="score"></div>
</div>
</div>
2. land不断向左移动
land.js
// 获取land元素
var land=document.getElementById('land');
// 设置背景移动的速度
let landSpeed=5;
// 定义landbac对象,添加属性x,x相当于land元素的背景图片的横坐标
var landbac={
x:0
}
// 设置循环定时器使land背景循环移动
var landMove=setInterval(landMoving(),30)
function landMoving(){
// land背景向左移动
landbac.x-=landSpeed;
if(landbac.x<-200) landbac.x=0;
// 实现真正的背景图片的移动
land.style.backgroundPositionX=landbac.x+'px';
}
背景移动
3. 小鸟煽动翅膀
bird.js
// 获取bird元素
var birdElem=document.getElementById('bird');
// 小鸟振翅飞翔
var j = 0;
let birdFly=setInterval(()=>{
birdElem.style.backgroundImage="url(../bird-game/image/bird0_" + j + ".png)"
j++;
if( j == 3){
j=0;
}
} , 100);
小鸟煽动翅膀
4. 小鸟改变颜色
根据小鸟图片命名可以得出规律:只需要改变路径中bird0,1,2的数值就可以实现小鸟颜色的切换,此时需要用到键盘监听,按下键盘的次数对应小鸟图片名称,从而实现颜色切换,又因为只有三种颜色,所以还需数值的循环,也就是0到1到2之后,再回到0。
event.js
//添加键盘监听事件,当按下上下键时切换颜色
var choice=0;
document.onkeydown=function(event){
// 小鸟改变颜色
if((event.keyCode==38||event.keyCode==40)){
choice++;
if(choice==3) choice=0;
}
}
bird.js
// 小鸟振翅飞翔+切换颜色
var j = 0;
let birdFly=setInterval(()=>{
birdElem.style.backgroundImage="url(../bird-game/image/bird"+choice+"_" + j + ".png)"
j++;
if( j == 3){
j=0;
}
} , 100);
4小鸟改变颜色
5. 小鸟下落
小鸟的下落是通过定时器,对小鸟纵坐标不断增加来实现。为了让小鸟的下落更贴近现实,给添加添加重力加速度,下落时小鸟速度越来越快,而上升时小鸟速度越来越慢。
// 小鸟添加重力
bird.js
let hasEnergy=false;
function changeSpeed(){
if (!hasEnergy) {
bird.ySpeed++;
bird.y += bird.ySpeed * 0.4;
}
else {
bird.ySpeed--;
bird.y -= bird.ySpeed * 0.1;
if (bird.ySpeed <= 0) {
hasEnergy = false;
}
}
}
// 定时器实现小鸟不断下降
game.js
setInterval(()=>{
changeSpeed();
birdElem.style.top=bird.y+'px';
},30)
小鸟下落
6. 鼠标监听,实现小鸟弹跳
event.js
var land=document.getElementById('land');
// 鼠标点击事件
land.addEventListener('click',function(){
bird.y-=20;
hasEnergy = true;
bird.ySpeed = 2;
});
7. 添加状态(游戏未开始,开始,暂停,结尾)
游戏未开始,小鸟在固定位置振翅飞行,land不动,可以切换颜色。
鼠标点击后开始,游戏开始,鼠标才能点击控制小鸟向上移动,land移动,管子循环出现。
空格控制暂停,小鸟在固定位置振翅飞行,land不动,管子不动。
游戏结束,小鸟不振翅飞行,land不动,管子不动,再次点击切换到游戏未开始状态。
game.js
// 设置游戏的状态 0:未开始 1:进行中 2:失败
let status=0;
// 判断游戏进行还是暂停
let running=false
setInterval(()=>{
if(status==0&&running==false){
// land不动
clearInterval(landMove);
}
if(status==1&&running==true){
// 小鸟不断下降
changeSpeed();
birdElem.style.top=bird.y+'px';
// land移动
landMove=setInterval(landMoving(),30);
}
if(status==2&&running==false){
end.style.display='block';
// 小鸟不振翅
clearInterval(birdFly);
// land不动
clearInterval(landMove);
}
},30)
event.js
var land=document.getElementById('land');
// 鼠标点击事件
land.addEventListener('click',function(){
if(status==0&&running==false){
status=1;
running=true;
start.style.display='none';
}
if(status==1&&running==true){
bird.y-=20;
hasEnergy = true;
bird.ySpeed = 2;
}
if(status==2&&running==false){
setTimeout(function(){
location.reload();
status=0;
bird.y=beginy;
},500);
}
});
event.js
//设置空格按下的次数
let count=0;
document.onkeydown=function(event){
// 空格键实现暂停
if(event.keyCode==32&&status==1){
if(count%2==0){
running=false;
pause.style.display='block';
clearInterval(landMove);
}
else{
running=true;
pause.style.display='none';
}
count++;
}
}
6添加状态
8. 创建水管+计分
pipe.js
// 设置初始分数为0
let scoreDemo=0;
// 设置水管移动速度
let pipeSpeed=3;
// 创建水管
function createPipe(position){
var pipe={};
pipe.x=position;
// 设置上管道的高度为150—250
pipe.upHeight=parseInt(Math.random()*100)+150;
// 设置管道之间的距离为100
// 得出下管道的高度
pipe.downHeight=500-pipe.upHeight-100;
// 得出下管道的x坐标
pipe.downposition=100+pipe.upHeight;
// 创建上管道
var upPipe=document.createElement('div');
// 设置上管道的宽高和绝对定位的位置
upPipe.style.width='52px';
upPipe.style.height=pipe.upHeight+'px';
upPipe.style.position='absolute';
upPipe.style.background='url(../bird-game/pipe2.png) no-repeat center bottom'
upPipe.style.left=pipe.x+'px';
upPipe.style.top='0px';
sky.appendChild(upPipe);
// 创建下管道
var downPipe=document.createElement('div');
// 设置下管道的宽高和绝对定位的位置
downPipe.style.width='52px';
downPipe.style.height=pipe.downHeight+'px';
downPipe.style.position='absolute';
downPipe.style.background='url(../bird-game/pipe1.png) no-repeat center top'
downPipe.style.left=pipe.x+'px';
downPipe.style.top=pipe.downposition+'px';
sky.appendChild(downPipe);
let scorePosition=position;
let pipeMove=setInterval(function(){
if(status==1&&running==true){
pipe.x-=pipeSpeed;
scorePosition-=pipeSpeed;
upPipe.style.left=pipe.x+'px';
downPipe.style.left=pipe.x+'px';
// 当管道恰好完全超出窗口时,改变管道位置,实现管道循环出现
if(pipe.x<-52){
pipe.x=800;
}
// 当管道恰好越过小鸟,分数加1
if(scorePosition+upPipe.offsetWidth<bird.x){
scoreDemo++;
scorePosition=800;
}
score.innerHTML='分数:'+scoreDemo;
}
},20);
}
// 控制水管出现时间
setTimeout(function(){
createPipe(600);
createPipe(800);
createPipe(1000);
createPipe(1200);
},100);
8创建水管
9. 碰撞检测
碰撞检测有两种情况:
1.小鸟与窗口顶部和land碰撞
game.js 定时器里
if(bird.y<0||(bird.y+birdElem.offsetHeight>500)){
status=2;
running=false;
bird.y=beginy;
clearInterval(birdFly);
clearInterval(landMove);
}
2.小鸟与管子碰撞
pipe.js createPipe()方法中
// birdElem.style.top=bird.y+'px';这条语句的作用:
// 如果小鸟和管道碰撞,则游戏结束,
// 此时小鸟的位置还是碰撞之前的位置,但是小鸟位置在数值上已经发生改变,出现显示上的错误,
// 因此再次将小鸟纵坐标上的值赋给birdElem.style.top,实现刷新
birdElem.style.top=bird.y+'px';
// 小鸟的中心X,Y坐标
let centerX1 = bird.x + birdElem.offsetWidth/2;
let centerY1 = bird.y + birdElem.offsetHeight/2;
// 上管道的中心X,Y坐标
let centerX2 = upPipe.offsetLeft + upPipe.offsetWidth / 2;
let centerY2 = upPipe.offsetTop + upPipe.offsetHeight / 2;
// 下管道的中心X,Y坐标
let centerX3 = downPipe.offsetLeft + downPipe.offsetWidth / 2;
let centerY3 = downPipe.offsetTop + downPipe.offsetHeight / 2;
let disX = Math.abs(centerX1 - centerX2); //小鸟和上管道中心点的横向距离
let disY = Math.abs(centerY1 - centerY2);//小鸟和上管道中心点的纵向距离
// 小鸟与上管道是否碰撞
let upHit=(disX<(birdElem.offsetWidth + upPipe.offsetWidth)/2)&&(disY<(birdElem.offsetHeight + upPipe.offsetHeight)/2);
let disX1 = Math.abs(centerX1 - centerX3);//小鸟和下管道中心点的横向距离
let disY1 = Math.abs(centerY1 - centerY3);//小鸟和下管道中心点的纵向距离
// 小鸟与上管道是否碰撞
let downHit=disX1<(birdElem.offsetWidth + downPipe.offsetWidth)/2&&disY1 < (birdElem.offsetHeight + downPipe.offsetHeight)/2;
if(upHit||downHit){
status=2;
running=false;
}
10.声音添加
html
<audio src="../bird-game/audio/begin.mp3" id="begin-audio"></audio>
<audio src="../bird-game/audio/change.mp3" id="change-audio"></audio>
<audio src="../bird-game/audio/fly.mp3" id="fly-audio"></audio>
<audio src="../bird-game/audio/hit.mp3" id="hit-audio"></audio>
<audio src="../bird-game/audio/score.mp3" id="score-audio"></audio>
<audio src="../bird-game/audio/over.mp3" id="over-audio"></audio>
js
var beginAudio=document.getElementById('begin-audio');
var changeAudio=document.getElementById('change-audio');
var flyAudio=document.getElementById('fly-audio');
var hitAudio=document.getElementById('hit-audio');
var scoreAudio=document.getElementById('score-audio');
var overAudio=document.getElementById('over-audio');
//分别添加到特定的需要音乐的位置
beginAudio.play();
changeAudio.play();
flyAudio.play();
hitAudio.play();
scoreAudio.play();
overAudio.play();
3.遇到的问题以及解决办法
1. 问题:小鸟碰撞到水管,但页面显示没碰撞
原因:小鸟和管道碰撞,游戏结束,此时小鸟的位置还是碰撞之前的位置,但是小鸟位置在数值上已经发生改变,出现显示上的错误.
解决方法:再次将小鸟纵坐标上的值赋给birdElem.style.top,实现刷新.
2. 游戏最后页面卡顿
原因:setinterval不会清除定时器队列,每重复执行1次都会导致定时器叠加,最终卡死你的网页.
解决方法:setInterval放在外层,内层配合使用setTimeout.
参考:
setInterval影响性能导致卡死的解决方法
JS setInterval 定时器导致页面卡死
3. 小鸟落地碰撞超出land
原因:小鸟上下改变了纵坐标,与land的碰撞条件是超出500就死亡,但是小鸟的纵坐标数值上不一定会正好等于界限500.
4. 改变鸟头方向,碰撞检测不准确
原因:通过旋转div改变鸟头方向,导致div的宽高发生改变,而碰撞检测是根据小鸟的宽高和水管的宽高检测的,因此碰撞检测不准确
解决方法:
1.将图片换成已经旋转过的小鸟
2.更改碰撞检测方法