美文网首页前端开发demojava code
JavaScript实现Fly Bird小游戏

JavaScript实现Fly Bird小游戏

作者: xhr_bird | 来源:发表于2016-12-12 01:21 被阅读9957次

    1.分析页面结构,理清需求和功能

    游戏有三个界面,分别是开始界面,游戏界面和游戏结束界面。

    1.1 开始界面

    start.gif
    • 游戏的大背景
    • 上下移动的游戏标题和翅膀摆动的小鸟
    • start 按钮,点击进入游戏界面
    • 一直移动的地面

    1.2 游戏界面

    play.gif
    • 显示越过障碍数量的计分器
    • 移动的障碍物,分别是上管道和下管道
    • 点击游戏界面,小鸟向上飞起,然后在重力作用下下坠,
    • 当小鸟和管道碰撞后,结束界面弹出,同时小鸟落到地面

    1.3 结束界面

    • Game over 提示面板
    • OK 按钮

    2. 开发“开始界面”

    考虑到草地的移动效果,我们在页面中加入两个草地

    2.1 HTML

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>Fly Bird</title>
            <link rel="stylesheet" type="text/css" href="css/index.css"/>
        </head>
        <body>
            <div id="wrapBg">  <!--游戏背景-->
                <div id="headTitle"> <!--开始标题-->
                    <img id="headBird" src="img/bird0.png" alt="小鸟" /> <!--标题中的小鸟-->
                </div>
                <button id="startBtn" ></button> <!--开始按钮-->
                <div id="grassLand1"></div> <!--草地1-->
                <div id="grassLand2"></div> <!--草地2-->
            </div>
        </body>
    </html>
    

    2.2 CSS

    #wrapBg{/*游戏背景*/
        width: 343px;height: 480px; 
        margin: 0 auto;
        background-image:url(../img/bg.jpg);
        position: relative;
        top: 100px;
        overflow: hidden;
    }
    #headTitle{/*开始标题*/
        width: 236px;height: 77px;
        background-image: url(../img/head.jpg);
        position: absolute; left: 53px; top: 100px;
    }
    #headBird{/*开始标题中的小鸟*/
        float:right;
        margin-top: 25px;
    }
    #startBtn{/*开始按钮*/
        width: 85px;height: 29px;
        padding: 0;margin: 0;
        background-image: url(../img/start.jpg);
        position: absolute;left: 129px;top: 250px;
    }
    #grassLand1{/*草地1*/
        height: 14px;width: 343px;
        background-image: url(../img/banner.jpg);
        position: absolute;top: 423px;
    }
    #grassLand2{/*草地2*/
        height: 14px;width: 343px;
        background-image: url(../img/banner.jpg);
        position: absolute;top: 423px;left: 343px;
    }
    

    将wrapBg中的overflow:hidden 注释掉的页面效果


    开始界面.jpg

    2.3 JS

    小鸟煽动翅膀的效果需要用到逐帧动画的原理

    逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的关键帧”中分解动画动作,也就是在时间轴的每帧上逐帧绘制不同的内容,使其连续播放而成动画。

    bird1.png
    bird0.png
    2.3.1 开始标题的摆动
            var jsHeadTitle = document.getElementById("headTitle");// 获取标题
            var jsHeadBird = document.getElementById("headBird"); // 获取标题中小鸟
            
            var Y = 3;//标题的摆动幅度
            var index = 0;
            var imgArr = ["img/bird0.png","img/bird1.png"] 
            //将小鸟图片路径放入一个数组,利用逐帧动画的原理做出小鸟翅膀摆动的样子
            var headWaveTimer = setInterval(headWave,200); //设置标题上下摆动的定时器
            function headWave() {
                Y *= -1;
                jsHeadTitle.style.top = jsHeadTitle.offsetTop + Y + "px";
                jsHeadBird.src = imgArr[index++];
                if (index == 2) {
                    index = 0;
                }
            }
    
    2.3.2 移动的草地
            var jsGrassLand1 = document.getElementById("grassLand1"); //获取草地1
            var jsGrassLand2 = document.getElementById("grassLand2"); //获取草地2
            
            var landTimer = setInterval(landRun,30); //让草地动起来的定时器
            function landRun() {
                if (jsGrassLand1.offsetLeft <= -343) {
                    jsGrassLand1.style.left = "343px";
                }
                if (jsGrassLand2.offsetLeft <= -343) {
                    jsGrassLand2.style.left = "343px";
                }
                jsGrassLand1.style.left = jsGrassLand1.offsetLeft - 3 + "px";
                jsGrassLand2.style.left = jsGrassLand2.offsetLeft - 3 + "px";
            }
    

    2.3.3 Start按键

            var jsStartBtn = document.getElementById("startBtn");
            jsStartBtn.onclick = function() { //为start按键添加点击事件处理程序
                jsHeadTitle.style.display = "none"; //隐藏标题
                clearInterval(headWaveTimer); //关闭让标题摆动的定时器
                jsStartBtn.style.display = "none"; //隐藏按键
                //待添加功能
                //点击开始按键进入游戏界面
            }
    

    完成后的效果(注释掉了wrapBg中的overflow:hidden )

    start01.gif

    接下来我们开发“游戏界面”

    3. “游戏界面”的开发

    游戏界面中有三样元素,分别是“小鸟”,“障碍”,和“计分器”,我们依次来创建相应的对象。

    3.1 小鸟

    首先,创建小鸟的对象,** bird.js **文件。

    var bird = {
        flyTimer:null,//小鸟飞翔定时器
        wingTimer:null,//小鸟翅膀摆动定时器
        
        div:document.createElement("div"),
        showBird:function(parentObj) {
            this.div.style.width = "40px";
            this.div.style.height = "28px";
            this.div.style.backgroundImage = "url(img/bird0.png)";
            this.div.style.backgroundRepeat = "no-repeat";
            this.div.style.position = "absolute";
            this.div.style.left = "50px";
            this.div.style.top = "200px";
            this.div.style.zIndex = "1";
            
            parentObj.appendChild(this.div);  //将小鸟DIV插入游戏界面中
        },
        
        fallSpeed: 0, //小鸟下落速度
        flyBird: function(){ //控制小鸟飞翔下落的函数
            bird.flyTimer = setInterval(fly,40);
            function fly() {
                bird.div.style.top = bird.div.offsetTop + bird.fallSpeed++ + "px";
                if (bird.div.offsetTop < 0) {  
                    bird.fallSpeed = 2; //这里用于控制小鸟不要飞出界面
                }
                if (bird.div.offsetTop >= 395) {
                    bird.fallSpeed = 0;
                    clearInterval(bird.flyTimer); //一旦飞到地面,清除定时器
                    clearInterval(bird.wingTimer); //清除翅膀摆动定时器
                }
                if (bird.fallSpeed > 12) {
                    bird.fallSpeed = 12;  //鸟的最大下落速度控制在12
                }
            }
        },
        
        wingWave: function() { //控制小鸟煽动翅膀的函数
            var up = ["url(img/up_bird0.png)", "url(img/up_bird1.png)"];
            var down = ["url(img/down_bird0.png)", "url(img/down_bird1.png)"];
            var i = 0, j = 0;
            bird.wingTimer = setInterval(wing,120);//逐帧动画,小鸟煽动翅膀
            function wing() {
                if (bird.fallSpeed > 0) {
                    bird.div.style.backgroundImage = down[i++];
                    if (i==2) {i = 0}
                }if (bird.fallSpeed < 0) {
                    bird.div.style.backgroundImage = up[j++];
                    if (j==2) {j = 0}
                }
            }
        },  
    };
    

    下面,实现点击start按钮时,加载小鸟。(在之前的代码基础上添加)

    jsStartBtn.onclick = function() { //为start按键添加点击事件处理程序
        jsHeadTitle.style.display = "none"; //隐藏标题
        clearInterval(headWaveTimer); //关闭让标题摆动的定时器
        jsStartBtn.style.display = "none"; //隐藏按键
        bird.showBird(jsWrapBg); //插入小鸟到界面中
        bird.flyBird(); //控制小鸟飞翔下落
        bird.wingWave(); //逐帧动画,小鸟煽动翅膀
        jsWrapBg.onclick = function(){
            bird.fallSpeed = -8;
        };
        //待添加功能
        //点击开始按键进入游戏界面
    }
    

    添加小鸟后的效果

    play01.gif

    3.2 障碍(上管道和下管道)

    block示意图.png

    障碍分为上管道和下管道,如示意图所示结构嵌套,这样就可以通过随机设置DownDiv2的高度和gapHeight的高度,来改变生成障碍的形态
    ** block.js **

    function Block() {
        this.upDivWrap = null;
        this.downDivWrap = null;
        this.downHeight = baseObj.randomNum(0,150);//随机生成0-150之间的数,用于控制下管道的高度
        this.gapHeight = baseObj.randomNum(150,160);// 管道中间间隙宽度,通过调节大小,可以的控制游戏难度
        this.upHeight = 312 - this.downHeight - this.gapHeight;
        
        // 用来生成Div的方法
        this.createDiv = function(url, height, positionType, left, top) {
            var newDiv = document.createElement("div");
            newDiv.style.width = "62px";
            newDiv.style.height = height;
            newDiv.style.position = positionType;
            newDiv.style.left = left;
            newDiv.style.top = top;
            newDiv.style.backgroundImage = url;  //"url(/img/0.jpg)"
            return newDiv;
        };
        
        this.createBlock = function() {
            var upDiv1 = this.createDiv("url(img/up_mod.png)", this.upHeight + "px");
            var upDiv2 = this.createDiv("url(img/up_pipe.png)", "60px");
            this.upDivWrap = this.createDiv(null, null, "absolute", "450px");
            this.upDivWrap.appendChild(upDiv1);
            this.upDivWrap.appendChild(upDiv2);//生成上方管道
            
            var downDiv1 = this.createDiv("url(img/down_pipe.png)", "60px");
            var downDiv2 = this.createDiv("url(img/down_mod.png)", this.downHeight +"px");
            this.downDivWrap = this.createDiv(null, null, "absolute", "450px", 363 - this.downHeight + "px");
            this.downDivWrap.appendChild(downDiv1);
            this.downDivWrap.appendChild(downDiv2); //生成下方的管道
            
            jsWrapBg.appendChild(this.upDivWrap);
            jsWrapBg.appendChild(this.downDivWrap);
        };
        
        this.moveBlock = function() { //控制管道移动的方法
            this.upDivWrap.style.left = this.upDivWrap.offsetLeft - 3 + "px";
            this.downDivWrap.style.left = this.downDivWrap.offsetLeft - 3 + "px";
        };  
    }
    

    公共对象文件 ** baseObj.js ** ,用来提供随机数,和两个矩形div的碰撞检测

    var baseObj = {
        //随机数
        randomNum: function(min, max) {
            return parseInt(Math.random() * (max - min + 1) + min);
        },
    
        //两个矩形元素之间的碰撞检测
        rectangleCrashExamine: function (obj1, obj2) {
                var obj1Left = obj1.offsetLeft;
                var obj1Width = obj1.offsetLeft + obj1.offsetWidth;
                var obj1Top = obj1.offsetTop;
                var obj1Height = obj1.offsetTop + obj1.offsetHeight;
    
                var obj2Left = obj2.offsetLeft;
                var obj2Width = obj2.offsetLeft + obj2.offsetWidth;
                var obj2Top = obj2.offsetTop;
                var obj2Height = obj2.offsetTop + obj2.offsetHeight;
    
                if (!(obj1Left > obj2Width || obj1Width < obj2Left || obj1Top > obj2Height || obj1Height < obj2Top)) {
                    return true;
                }
                return false;
        },
    };
    

    下面我的想法是在start按钮点击的时候创建一个block,把这个block存储到数组* blocksArr 中,在 landTimer 定时器的方法 landRun *中检查此数组的长度,如果数组不为空数组,那么就让数组中所有的block移动。

    检查数组中最后一个block离开的距离,达到一定距离,就重新new 一个block,添加到数组。

    检查数组中第一个block,一旦达到一定位置,就在结构中移除downDivWrap 和 upDivWrap,同时在数组中删除block。

            var blocksArr = [];
            var blockDistance = baseObj.randomNum(130,250);
            var landTimer = setInterval(landRun,30); //让草地动起来的定时器
            function landRun() {
                if (jsGrassLand1.offsetLeft <= -343) {
                    jsGrassLand1.style.left = "343px";
                }
                if (jsGrassLand2.offsetLeft <= -343) {
                    jsGrassLand2.style.left = "343px";
                }
                jsGrassLand1.style.left = jsGrassLand1.offsetLeft - 3 + "px";
                jsGrassLand2.style.left = jsGrassLand2.offsetLeft - 3 + "px";
                
                if (blocksArr.length) {
                    for (var i = 0; i < blocksArr.length; i++) {
                        blocksArr[i].moveBlock();
                        var x =baseObj.rectangleCrashExamine(blocksArr[i].downDivWrap, bird.div);
                        var y = baseObj.rectangleCrashExamine(blocksArr[i].upDivWrap, bird.div);
                        var z = bird.div.offsetTop >= 390;
                        if (x || y || z) {
                            window.clearInterval(landTimer);//清除landTimer定时器
                            bird.fallSpeed = 0; //小鸟下落
                            jsWrapBg.onclick = null; //消除点击事件
    
                        }
                    }
                    if (blocksArr[blocksArr.length - 1].downDivWrap.offsetLeft < (450 - blockDistance)) {
                            blockDistance = baseObj.randomNum(130,250);
                            var newBlock = new Block();
                            newBlock.createBlock();
                            blocksArr.push(newBlock);
                    }
                    
                    if (blocksArr[0].downDivWrap.offsetLeft < -50) {
                            jsWrapBg.removeChild(blocksArr[0].downDivWrap);
                            jsWrapBg.removeChild(blocksArr[0].upDivWrap);
                            blocksArr.shift(blocksArr[0]);
                    }
                }
            }
    

    当前的游戏效果

    play02.gif

    3.3 计分器

    游戏中的计分器相对较好实现,我们就实现最大为三位数的计分器吧。
    ** html **

            <div id="score">
                <div id="num1"></div>
                <div id="num2"></div>
                <div id="num3"></div>
            </div>
    

    css样式

    #score{
        position:absolute;
        left: 130px;
        top:50px;
        z-index: 1;
    }
    
    #score div{
        height: 39px;
        width: 28px;
        float: left;
        background-image: url(../img/0.jpg);
        display: none;
    }
    

    js

            var jsScore = document.getElementById("score");
            var jsNum1 = document.getElementById("num1");
            var jsNum2 = document.getElementById("num2");
            var jsNum3 = document.getElementById("num3");
            var score = 0;
    

    实现计数器功能,最重要的是如何判断走过水管的数量,我们以水管的位置来判断。bird的定位left为50px,水管的宽度是62px,当水管越过小鸟的时候,水管距离它父级的定位 *offsetLeft *是 -12px。每当有一个水管到达此位置,score++;
    在start按钮的事件处理程序中加入

    jsNum1.style.display = "block";// 在点击开始之后,让计数器显示出来。
    
           if (blocksArr[0].downDivWrap.offsetLeft == -12) {
                    score++;//积分面板
                    if (score < 10) {
                    jsNum1.style.backgroundImage = "url(img/" + score + ".jpg)";
                } else if (score < 100) {
                    jsNum2.style.display = "block";
                    jsNum1.style.backgroundImage = "url(img/" + parseInt(score/10) + ".jpg)";
                    jsNum2.style.backgroundImage = "url(img/" + score%10 + ".jpg)";
                } else if (score < 1000) {
                    jsNum3.style.display = "block";
                    jsNum1.style.backgroundImage = "url(img/" + parseInt(score/100) + ".jpg)";
                    jsNum2.style.backgroundImage = "url(img/" + parseInt(score/10)%10 + ".jpg)";
                    jsNum3.style.backgroundImage = "url(img/" + score%10 + ".jpg)";
                }
                    console.log(score);
            }
    

    目前效果 ,计数器功能完成。

    play03.gif

    4.“结束界面”的开发

    当小鸟和管道碰撞或者和地面碰撞时候,隐藏计分器,弹出结束面板。
    结束界面主要有“结束面板”和“ok”按钮,这里需要为“ok”按钮添加点击事件。

                <div id="gameOver">
                    <img src="img/game_over.jpg" alt="game over" />
                    <img src="img/message.jpg" alt="message" />
                    <img id="ok" src="img/ok.jpg" alt="ok" />
                </div>
    
    #gameOver{
        position: absolute;
        top: 100px;
        text-align: center;
        display: none;
        z-index: 1;
    }
    

    为“OK”按钮添加事件

            jsOkBtn.onclick = function() {
                window.location.href = "index.html"; //刷新页面
            }
    

    最终效果

    play04.gif

    有兴趣的朋友,可以下载代码,然后加上音效。OK,结束。

    GitHub代码下载传送门

    相关文章

      网友评论

      • 谦龙:有很多动画可以用css实现的,尽量不用js
      • 徐丶清风:楼主,3.2还有一步,就是在jsStartBtn.onclick函数中写入,你这里只说了一句,没有贴代码:
        var b = new Block();
        b.createBlock();
        blocksArr.push(b);
        辛辛苦苦撸了很久代码想膜拜一下楼主,然后中途找了好久bug。。。:flushed: 我的内心是崩溃的
      • 一个小小的蚂蚁:楼主,我也实现了小鸟一个同样的游戏,不过整体不如你的,特别是小鸟煽动翅膀那里,我压根没考虑,我想问的是你如何处理小鸟碰撞检测的,因为小鸟是个不规则的形状,特别是额头那里,如果时矩形那就好处理。
        xhr_bird: @fantasy525 你说的没错,小鸟确实是一个方块,我的也是,我的做法是,缩小div的尺寸,我的也有同样问题,只是不明显而已。😀
      • 布蕾布蕾:明就动手做,做出来贴个图片,然后,今天刚写了react入门总结,用这个练手不知道效果怎么样。很期待!
      • ca7d95feead0:我是学java的,虽然看这个有些不太明白,但可以回头研究研究。前段知识太少…
        xhr_bird: @ArnoTo 前端,做的东西可以直接看到,喜欢。
        ca7d95feead0: @Runner_Yang 现在怎么想学前段了呢?
        xhr_bird:@ArnoTo 嗯,我之前也在自学java
      • glassyw:厉害了~
      • 8095a0dfbe5b:昨天晚上我模糊糊记得你好像回复了我另一段,是我的幻觉么?😂
      • 8f2c06c99cb6:可以给个下载代码的地址么
        8f2c06c99cb6:好的,谢谢
        xhr_bird: @瑛0130 戳文章最后一句话。github下载
      • 伏晶之心:给您360个赞,赶紧下载,快到碗里来 :stuck_out_tongue:
      • crazymew:666666,学习了:)
      • 8095a0dfbe5b:虽然看不懂,但是很想学。
        8095a0dfbe5b:@Runner_Yang 嗯嗯,初学者应该怎么学起才不容易学死呢?
        xhr_bird:@PIXIN 想学就学学看,网上有很多资源,种树最好的两个时间,一是十年前,二是现在。
      • eebichu:棒棒哒
      • 此鹿不通:按照例子所发源码去打,但是小鸟飞不起来,console速度也有变化,但是变化之后瞬间就按原来的循环继续往下走了,请问一下知道是什么原因吗?
        xhr_bird:@此鹿不通 代码可以下载了
        此鹿不通:@Runner_Yang 知道了,谢谢
        xhr_bird: @此鹿不通 没有点击事件处理程序
      • e6f5470edfc3:楼主,可不可以邀请你做我的专栏作家
      • 极乐君:厉害了!请问可以转载吗?会注明来源和作者的~
        极乐君:@Runner_Yang 好滴
        xhr_bird: @极乐君 先不要转载,这只是半篇,等我周末补齐。
      • 艾斯特Esther:厉害厉害!!!
      • 27b4a30242ce:求源码 576424292@qq,我以前用canvas写了个这个。
        27b4a30242ce:@Runner_Yang 嗯嗯,谢谢
        xhr_bird: @oceanaly 666
        xhr_bird: @oceanaly 最近比较忙,等周末做好了上传。
      • 儿桃说故事:求源代码
        xhr_bird:@儿桃 可以下载代码了
        儿桃说故事: @Runner_Yang 嗯嗯,好的辛苦,大神嘿嘿
        xhr_bird: @儿桃 最近比较忙,等周末做好了上传。
      • Klart:学了js有一段时间了,只会做网页上的,游戏还没做过,求源码分享
        xhr_bird:@Klart 可以下载代码了
        xhr_bird: @Klart 最近比较忙,等周末做好了上传。
      • Klart:求源码
      • 大幸运啵啵啵:楼主真厉害,辛苦大神了 :+1: :+1: :+1:
      • 760fa65c07ec:大神会VS么?
      • 郁金社长:写的真不错,谢谢分享,小哥
      • 不悔将来:大神,请收下我的膝盖
      • 4588e4274830:刚好最近在学JavaScript
        xhr_bird: @代码描绘人生 可以下载代码了
      • 67f4b6100ddb:坐等。。。。
        xhr_bird: @童小乐 可以下载代码了
        xhr_bird: @童小乐 最近比较忙,等周末做好了上传。
      • IMWNK:大大的赞👍
      • ejxbxj:编程可以自学吗?C语言应该先学啥
        xhr_bird: @鱼阿魚 学感兴趣的,语言有很多种。
      • ejxbxj:牛逼啊~膜拜大神😁
      • 伏晶之心:看得您步骤写得这么详细 :+1: ,好想玩这个游戏,给个下载地址呗 :smiley:
        xhr_bird: @阿福兵哥 可以下载代码了。
        伏晶之心:期待中。。。
        xhr_bird: @阿福兵哥 最近比较忙,等周末做好了上传。
      • 花生陪米饭:😀搜嘎
      • Mystic_1:可以的
      • 每每相遇如初见:太坏了,,竟然没写完,,哈哈,,加油,,
        xhr_bird: @每每相遇如初见 写完了,代码可下载
      • 不想说话34567:前端样式全是手写吗 有什么ide可用吗
        不想说话34567: @Runner_Yang 谢谢 我尝试下
        xhr_bird: @不想说话34567 初学的话用Sublime text3也很好。可以下载好多插件。
        xhr_bird: @不想说话34567 写这个游戏的时候我用的是Hbuilder
      • 于连林520wcf:保持关注
        xhr_bird: @于连林520wcf Thanks
      • 02c5cfc10d53:牛逼!
        xhr_bird: @大海一余 ʘᴗʘ 昨天晚上码字,太累,后续会加详细
      • 8fd9690ae7dd:很棒,讲解得也很详细,期待你的后续😀
        xhr_bird: @郎小艾 后面加详
      • aef70f302bc2:详不错,期待后续
        xhr_bird: @runnoob 等我周日总结出来上传,这几天忙。
        Pehd:@Runner_Yang 求个github地址
        xhr_bird: @棱建 嗯,今天太晚了,改天续全套素材和代码。
      • xhr_bird:我大半夜占,有人看,再更新,累。

      本文标题:JavaScript实现Fly Bird小游戏

      本文链接:https://www.haomeiwen.com/subject/oqgypttx.html