原生JS实现FlappyBird游戏 超详细解析 快来做一个自己玩吧

var timer = setInterval(function(){

bgMove();

},30)

function bgMove() {

var bg = document.querySelector(‘.contain’);

bgDis -= 5;

bg.style.backgroundPosition = ${bgDis}px 0;

}

在我们制作的这个游戏中,不论是背景移动还是待会要做的管道的移动,小鸟的移动,最后封装的函数都需要在这个定时器里调用,这样才会有我们看到的那种动画一样的效果。

3.管道的创建与移动💨


在实现管道的移动之前我们先需要创建管道,因为我们要让生成的管道高度不一致,所以需要先写一个随机数函数,我们就在public.js里完成:

function rand(min, max) {

return Math.round(Math.random() * (max-min) + min);

}

我们先整理一下创建管道的思路:

  1. 先写管道的样式,html与css部分的代码在文章最后都有,大家在看解析的时候要先看一眼html结构和css样式。

  2. 规定上下管道间隔120px,通过定义好的rand随机函数实现上管道高度随机,背景图片高度减去间隔减去上管道高度就是下管道高度,这里下管道高度不要给随机。

  3. 通过insertAdjacentHTML将生成管道的代码添加到ul里

因为管道也是不断生成的,我们需要在timer定时器里调用管道移动函数pipeMove():

var timer = setInterval(function(){

bgMove();

pipeMove();

},30)

我们在外面定义这个pipeMove方法,在pipeMove里完成管道的创建与移动

function pipeMove() {

//1.创建管道

createPipe();

//2.管道移动

}

下面先来根据在开头写的思路来完善管道创建函数createPipe:

function createPipe() {

var pipeheight = rand(100,300);

var ul = document.querySelector(‘ul’);

var str = <li class="top" style="height:${pipeheight+'px'};left:${sw+'px'}"><div></div></li><li class="bottom" style="height:${sh-pipeheight-120+'px'};left:${sw+'px'}"><div></div></li>;

ul.insertAdjacentHTML(‘beforeend’,str);

}

运行代码看一看管道有没有被创建出来:

很明显管道数量太多了啊,因为定时器每隔三十毫秒就会调用管道创建函数,所以管道生成的就非常多,我们定义一些全局变量进行限制:

var space = 100; //创建管道的间隔

var count = 0; //管道的计数

修改一下createPipe函数,当计数达到创建管道的间隔100时才执行下面创建管道的代码,否则不执行,这样就对生成管道的数量进行了限制

function createPipe() {

count ++;

if (count != space) {

return ;

}

count = 0;

var pipeheight = rand(100,300);

var ul = document.querySelector(‘ul’);

var str = <li class="top" able="0" style="height:${pipeheight+'px'};left:${sw+'px'}"><div></div></li><li class="bottom" style="height:${sh-pipeheight-120+'px'};left:${sw+'px'}"><div></div></li>;

ul.insertAdjacentHTML(‘beforeend’,str);

}

现在管道可以在背景的右面不断的生成,这样管道的创建就全部完成了,下面在pipeMove方法里继续完善管道移动的部分:

function pipeMove() {

//1.创建管道

createPipe();

//2.管道移动

var li = document.querySelectorAll(‘li’);

li.forEach(function(value,index,arr){

arr[index].style.left = arr[index].offsetLeft-2+‘px’;

})

}

我们先获取创建的所有管道,然后通过foreach循环每次调用都让管道左移两像素,管道就能成功移动起来了。

注意:直接通过obj.style.left和obj.style.top可以获取位置,但是有局限性,这种获取的方法只能获取到行内样式的left和top的属性值,不能获取到style标签和link 外部引用的left和top属性值。所以这里用offsetleft获取

然后我们再给管道加一个边界,让他超出背景时就在ul里删除这个元素:

li.forEach(function(value,index,arr){

arr[index].style.left = arr[index].offsetLeft-5+‘px’;

if(arr[index].offsetLeft<=-62) {

ul.removeChild(arr[index]);

}

})

我们运行代码看一看效果:

这样管道的创建与运动就基本上完成了,下面我们开始小鸟的操作。

4.小鸟操作💨


首先我们先把小鸟的html结构和css样式搭建好,下一步就是让小鸟“动起来”。

同样我们需要在定时器中调用小鸟移动函数birdMov

var timer = setInterval(function(){

bgMove();

pipeMove();

birdMove();

},30)

因为游戏开始的时候小鸟要向下掉落所以先写一个小鸟的移动函数:

function birdMove() {

var bird = document.querySelector(‘#bird’);

bird.style.top = bird.offsetTop +5 + ‘px’;

}

这样的话,小鸟就实现了一直匀速下落的效果,但是游戏中我们的小鸟并不是匀速的所以我们还需要定义一个全局变量speed,初始化它的值为0,来控制小鸟的速度。

因为我们在游戏中单击屏幕时小鸟会向上飞而且向上飞的速度和向下的速度不相同,所以我们在全局声明一个isDown变量,来判断小鸟是否向下飞。默认isDown = true,因为小鸟不操作的话一定是向下飞的。

function birdMove() {

if(isDown) {

speed += 0.4;

speed = speed > 8 ? 8 : speed;

}

else{

speed += 0.7;

}

var bird = document.querySelector(‘.bird’);

bird.style.top = bird.offsetTop +speed + ‘px’;

}

如果不点击屏幕每隔三十毫秒小鸟的速度就增加0.4,然后用一个三元表达式,如果速度达到8那么就是小鸟的极限速度就不再增加了,最后我们把原来的5这个固定值换成speed就实现了小鸟速度的动态变化

接下来我们要实现的是在单击背景的任一处时小鸟能够向上移动,所以我们需要给背景图片一个点击事件:

var contain = document.querySelector(‘.contain’);

contain.addEventListener(‘click’,function() {

isDown = false;

speed = -8;

})

当我们点击屏幕时,小鸟要向上飞,所以isDown被赋为false,然后立刻给一个向上的位移距离为8

我们运行一下代码看看效果:

是不是已经有那么点感觉了,但是小鸟点击屏幕的时候头会向上抬,所以还得在点击屏幕的时候改小鸟的背景图片

我们创建两个类,一个birddown类里面是小鸟头向下的图片,一个birdup类里面是小鸟头向上的。

.birddown {

background: url(./img/down_bird0.png);

}

.bird_up {

background: url(./img/up_bird0.png);

}

然后给bird添加默认样式类birddown,这样当我们点击屏幕时,就修改bird的类为birdup:

contain.addEventListener(‘click’,function() {

isDown = false;

speed = -8;

var bird = document.querySelector(‘#bird’);

bird.className = ‘birdup’;

})

这样的话我们在点击屏幕的时候小鸟就从头向下变成头向上了,但是如果不点击屏幕的时候小鸟还是应该回到默认向下的样式,因为不点击屏幕小鸟就会向下飞,那我们想想这个怎么实现呢?

那小鸟什么时候向下飞呢,就是speed为0的时候,我们每次点击屏幕的时候小鸟的速度都是-8,但是我们一直在调用birdmove,每次speed都加0.7,这样向上的速度总会越来越小然后当大于0 的时候小鸟就向下飞。

这样我们就在birdmove里补全代码实现点击屏幕小鸟就抬头向上飞,下降就低头向下飞:

function birdMove() {

var bird = document.querySelector(‘#bird’);

if(isDown) {

speed += 0.4;

speed = speed > 8 ? 8 : speed;

}

else{

speed += 0.7;

if(speed>=0) {

speed = 0;

isDown = true;

bird.className = ‘birddown’;

}

}

var bird = document.querySelector(‘#bird’);

bird.style.top = bird.offsetTop +speed + ‘px’;

}

我们看一下效果:

这样我们小鸟的动作就基本写完了,现在需要当小鸟触顶和触底的时候应该让游戏gameover。

把下面判断边界的代码放在birdmove里,这样每次先判断一下是否超出边界,如果超出的话就直接gameover并清除定时器然后执行again函数重新开始游戏。

if(bird.offsetTop<0||bird.offsetTop>sh-30) {

alert(‘gameover’);

clearInterval(timer);

again();

return;

}

再外面创建这个again函数来实现重新开始游戏:

function again() {

bgDis = 0;//bg的移动距离

count = 0; //管道的计数

isDown = true;//判断是否向下飞

speed = 0;//控制小鸟的速度

var ul = document.querySelector(‘ul’);

ul.innerHTML = ‘’;//清空管道

var bird = document.querySelector(‘#bird’);

bird.style.top = 100+‘px’;

start()

}

我们在again函数里要重新初始化一些变量,这里有些变量如管道间隔或者背景宽高是不需要再次初始化的。并且不用带var,因为如果带var了就是局部变量,但这里我们要改变的是全局变量。

然后我们需要清空所有画面中的管道,也就是ul里的内容。然后把小鸟的高度恢复到最开始的距离顶端20像素的位置。

最后调用了一个start函数,这个start函数就是把最开始的计时器给封装了:

function start() {

timer = setInterval(function(){

bgMove();

pipeMove();

birdMove();

},30)

}

因为在游戏结束的时候我们清空了计时器,所以重新开始的时候我们得再次调用这个计时器。

注意:计时器变量timer不应该加var,因为封装后加了var的在函数里就不是全局变量了

这样当我们的小鸟在触顶或者触底的时候就会弹出gameover对话框点击确定然后就重新开始游戏

然后我们把原来写的birddown类和birdup类修改为动画

@keyframes birddown {

from {

background-image: url(img/down_bird0.png);

}

to {

background-image: url(img/down_bird1.png);

}

}

@keyframes birdup {

from {

background-image: url(img/up_bird0.png);

}

to {

background-image: url(img/up_bird1.png);

}

}

从bird0到bird1就是小鸟的翅膀有个变化,这样加上动画后小鸟就像在飞动翅膀。

.birddown {

animation: birddown 0.05s linear infinite;

}

.birdup {

animation: birdup 0.05s linear infinite;

}

5.碰撞检测💨


如何判断触顶或者触底对我们来说并不难,但是如何判断小鸟和管道相撞呢?

下面我们回到public.js文件里写一下这个碰撞检测函数isCrash,这个函数同样是复用性很高的。

function isCrash(a,b) {

var l1 = a.offsetLeft;

var t1 = a.offsetTop;

var r1 = l1 + a.offsetWidth;

var b1 = t1 + a.offsetHeight;

var l2 = b.offsetLeft;

var t2 = b.offsetTop;

var r2 = l2 + b.offsetWidth;

var b2 = t2 + b.offsetHeight;

if (r2<l1 || b2<t1 || r1<l2 || b1<t2) {

// 不碰撞

return false;

} else {

// 碰撞

return true;

}

}

在if语句里只要有一个条件不满足就说明不会碰撞,这个很好理解,这里我们就分析一下为什么r2<l1就说明不会碰撞呢?如果这里a代表管道,b代表小鸟,那么l1就是管道到左侧背景的距离,l2代表小鸟到背景左侧的距离,那么r2<l1的意思就是小鸟本身的宽度再加上小鸟到背景左侧的距离比管道到背景左侧的距离还小,这样二者肯定不会碰上,所以其他方向同理。

然后我们在开始函数里再调用一下check()函数:

function start() {

timer = setInterval(function(){

bgMove();

pipeMove();

birdMove();

check();

},30)

}

check()函数通过调用isCrash来看所有的管道和小鸟有没有碰撞,如果有的话就gameover那一套

function check() {

var bird = document.querySelector(‘#bird’);

var li = document.querySelectorAll(‘li’);

li.forEach(function(value,index,arr){

if(isCrash(arr[index],bird)){

alert(‘gameover’);

clearInterval(timer);

again();

return;

}

})

}

我们来看一看效果,看看碰撞检测有没有实现:

这样我们所有的碰撞检测都完成了

6.触屏事件💨


因为小鸟只添加了点击事件,所以如果移动端下的话我们还得添加触屏事件:

contain.touchstart = function(e) {

e.preventDefault();

isDown = false;

speed = -8;

var bird = document.querySelector(‘#bird’);

bird.className = ‘birdup’;

}

我们把点击事件里的代码复制到触屏事件里就可以啦,因为在移动端下我们双击屏幕时屏幕会放大,所以我们要阻止默认事件

值得注意的是我们在最开始定义管道的样式时管道的left值是320px,是我们在pc端下的背景宽度,但是在移动端下屏幕宽度不一,所以我们要把默认的320px删掉,在管道生成的函数里定义管道的left值为sw,因为我们在移动端下sw的值就是屏幕的宽度。

7.制作开始与结束面板💨


下面我们先开始制作开始面板,先写完开始面板的样式,然后要做的就是这种上下动的效果,这里还需要我们再用到css的动画效果

定义两个动画,给开始面板的盒子一个move动画这样就实现了上下动的效果,再给小鸟的盒子添加bird动画,这样小鸟就可以扑哧翅膀了

@keyframes bird {

from {

background-image: url(img/bird0.png);

}

to {

background-image: url(img/bird1.png);

}

}

@keyframes move {

from {

transform: translateY(-2rem);

}

to {

transform: translateY(2rem);

}

}

现在我们需要给开始面板的ok按钮一个点击事件,当我们点击开始按钮时要不显示开始面板,然后再显示小鸟,再调用start函数来开始游戏

下面是开始面板的ok点击事件,因为btn按钮有两个一个是开始面板的一个是结束面板的所以btn[0]就是开始面板的按钮

var btn = document.querySelectorAll(‘.but’);

btn[0].addEventListener(‘click’,function() {

var start1 = document.querySelector(‘#start’);

var bird = document.querySelector(‘#bird’);

start1.style.display = ‘none’;

bird.style.display = ‘block’;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

今天的文章可谓是积蓄了我这几年来的应聘和面试经历总结出来的经验,干货满满呀!如果你能够一直坚持看到这儿,那么首先我还是十分佩服你的毅力的。不过光是看完而不去付出行动,或者直接进入你的收藏夹里吃灰,那么我写这篇文章就没多大意义了。所以看完之后,还是多多行动起来吧!

可以非常负责地说,如果你能够坚持把我上面列举的内容都一个不拉地看完并且全部消化为自己的知识的话,那么你就至少已经达到了中级开发工程师以上的水平,进入大厂技术这块是基本没有什么问题的了。

start1.style.display = ‘none’;

bird.style.display = ‘block’;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-P37EJM0z-1713732695221)]

[外链图片转存中…(img-XV5Oi9fG-1713732695222)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

[外链图片转存中…(img-ynpCawV8-1713732695222)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

[外链图片转存中…(img-IOkzYW5a-1713732695223)]

最后

今天的文章可谓是积蓄了我这几年来的应聘和面试经历总结出来的经验,干货满满呀!如果你能够一直坚持看到这儿,那么首先我还是十分佩服你的毅力的。不过光是看完而不去付出行动,或者直接进入你的收藏夹里吃灰,那么我写这篇文章就没多大意义了。所以看完之后,还是多多行动起来吧!

可以非常负责地说,如果你能够坚持把我上面列举的内容都一个不拉地看完并且全部消化为自己的知识的话,那么你就至少已经达到了中级开发工程师以上的水平,进入大厂技术这块是基本没有什么问题的了。

资料领取方式:戳这里前往免费领取

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值