• JavaScript操作canvas制作前端H5小游戏——Flappy Bird


    游戏查看

    源码和素材下载

    博主学习前端一年多一点,还是个新手,不过勇于尝试,才能不断进步,如果代码质量不好,欢迎提意见,下面开始讲解,首先贴张游戏界面图:

    这里写图片描述

    游戏使用canvas画图制作,分析游戏确定有这几个元素:

    1. 天空背景不动
    2. 小鸟上下移动,左右不动
    3. 地板和水管向左移动(造成小鸟向前移动的错觉)

    canvas画图是覆盖画图,所以画图顺序很重要,它的api我就不多说了,只有用到以下内容:

    <!-- html代码 -->
    <canvas id="canvas">您的浏览器不支持canvas</canvas>
    /* js相关 */
    var canvas = document.getElementById('canvas'),   //获取canvas节点
        ctx = canvas.getContext('2d');                //获取画布上下文画图环境
    //画图(只举了一种,还有另一种传参方式)
    ctx.drawImage(img, imgx, imgy, imgw, imgh, canx, cany, canw, canh);
    //参数的含义依次为:图片资源、图片的x坐标、y坐标、宽度、高度、画布中x坐标、y坐标、宽度、高度
    //因为我把所有的图片都合成一张,所以需要用截取图像的传参方式

    下面简单说说整个游戏的运行代码结构:

    var img = new Image();           //加载图像
    img.src = './img.png';             
    img.onload = start;              //图像加载完成就运行start函数,所以start是入口
    
    function start(){
        //检查是否碰撞到地板,水管
        check();
        if(是否游戏结束){
            //游戏结束的操作然后退出
            return;
        }
        //画背景
        ...
        if(isStarted){            //isStarted为是否开始游戏的变量,是全局的,默认为false
            //开始游戏就画小鸟,水管
        }else{
            //否则就画准备开始的图像
        }
        //画分数(默认为0,准备阶段画的是0...
        //画地板
        ...
        //设置定时器,保证动画在游戏中不断进行
        timer = requestAnimationFrame(start); //和setTimeout(start, 16)效果差不多
    }
    
    document.ontouchstart = document.onmousedown = function(e){
        //点击屏幕时小鸟进行跳跃等处理
    }

    整体结构就是这样,然后我们一部分一部分完成就可以了。

    第一步:获取设备的屏幕大小,兼容各种屏幕设备

    var viewSize = (function(){
    
        var pageWidth = window.innerWidth,
            pageHeight = window.innerHeight;
    
        if (typeof pageWidth != 'number') {
            pageHeight = document.documentElement.clientHeight;
            pageWidth = document.documentElement.clientWidth;
        };
    
        if(pageWidth >= pageHeight){
            pageWidth = pageHeight * 360 / 640;
        }
        pageWidth = pageWidth >  414 ? 414 : pageWidth;
        pageHeight = pageHeight > 736 ? 736 : pageHeight;
    
        return {
             pageWidth,
            height: pageHeight
        };
    
    })();
    //然后就设置画布宽高
    canvas.width = viewSize.width;
    canvas.height = viewSize.height;
    //定义原图像与游戏界面的像素比
    var k = viewSize.height / 600    //我找的背景图高度为600px,所以比例就是屏幕高除以600

    第二步:完成游戏进行中的部分(没有gameover检查,isStarted为true时)

    1)画背景(没有难点,主要是图像大小的计算要想清楚)

    //清除
    ctx.clearRect(0,0,viewSize.width,viewSize.height);
    //画背景
    ctx.drawImage(img, 0, 0, 800, 600, 0, 0, Math.ceil(k * 800), viewSize.height);

    2)画小鸟:我在全局定义了一个小鸟类,如下:

    function Bird(){
        //小鸟拍翅膀有三种状态,所以画图相关大多用一个数组来表示
        this.imgX = [170, 222, 275];                           //在原图中x的坐标
        this.imgY = [750, 750, 750];                           //在原图中y的坐标
        this.imgW = [34, 34, 34];                              //在原图中宽度
        this.imgH = [24, 24, 24];                              //在原图中高度
        var canX = Math.ceil(110 / 450 * viewSize.width);      //在画布中x的坐标
        this.canX = [canX, canX, canX];
        var canY = Math.ceil(380 / 800 * viewSize.height);     //在画布中y的初始坐标 
        this.canY = [canY, canY, canY];                        
        var canW = Math.ceil(34 * k);                          //在画布中的宽度  
        this.canW = [canW, canW, canW];                 
        var canH = Math.ceil(24 * k);                          //在画布中的高度
        this.canH = [canH, canH, canH];
        //下面三个变量是用来协助记住是在三个状态中的哪个状态,后面一看就知道了
        this.index = 0;                                        
        this.count = 0;
        this.step = 1;
        //表示小鸟飞行的时间,后面知道用途
        this.t = 0;
        //记住初始y坐标,也是后面一看就知道了
        this.y = [canY, canY, canY];
    }

    定义类的好处就是可以不用设置那么多的全局变量,你可以直接定义小鸟为一个对象,接着定义小鸟画图方法:

    Bird.prototype.draw = function(){
        var index = this.index;
        //翅膀拍动, this.count就是用来控制拍动的频率,记住定时器1秒运行16帧,频率很快的
        this.count++;
        if(this.count == 6){
            this.index += this.step;   
            this.count = 0;
        }
        //this.index的变化过程为0、1、2、1、0、1、2、1...所以需要this.index +1和-1变化
        if((this.index == 2 && this.step == 1) || (this.index == 0 && this.step) == -1){
            this.step = - this.step;
        } 
        //计算垂直位移,使用公式 y = a * t * (t - c),这里就知道了this.t是代表着小鸟起跳后到现在的时间
        //我使用了抛物线的函数方程,你也可以自己选择,代码下面我会给出函数坐标图就很清除了
        var c = 0.7 * 60;
        var minY = - 85 * viewSize.height / 800;
        var a = -minY * 4 / (c * c);
        var dy = a * this.t * (this.t - c);  //dy是小鸟的位移
        //下面是小鸟飞到顶部的情况,我的处理是,使再点击失效,要小鸟飞下来才能继续点击
        if(this.y[0] + dy < 0){
            canClick = false;
        }else{
            canClick = true;
        }
        //然后小鸟在画布的y坐标就等于原先的y坐标加上位移
        for(var i = 0; i < 3; i++){
            this.canY[i] = this.y[i] + Math.ceil(dy);
        }
        this.t++;
        ctx.drawImage(img, this.imgX[index], this.imgY[index], this.imgW[index], 
                           this.imgH[index], this.canX[index], this.canY[index], 
                           this.canW[index], this.canH[index]);
    
    };

    给出小鸟计算方程的坐标图

    这里写图片描述

    因为canvas的y正方向是向下的,所以跳跃应该位移是先负后正,自由落体又是抛物线,接下来就是数学知识了,图中可以看出:

    • 如果this.t > c,dy > 0,所以可以得出,当this.t = c小鸟到最高点,选c的大小就可以控制上升和下落的速度

    • 在this.t = c/2时,dy达到了最小值,所以,控制Ymin可以确定小鸟的垂直移动最大距离。

    要画小鸟就可以:

    var bird = new Bird();
    bird.draw();

    3)画水管:

    游戏画面中最多出现两组水管,当第一组水管到中间时,第二组开始出现,当第一组水管从游戏界面的左边出去了,第二组水管刚刚到达中间,而最右边又开始有水管进来,以此类推,不断重复。

    这里写图片描述

    先解决一组水管的画法,仍然先定义水管类,分为上水管和下水管:

    //基类,属性的含义同小鸟类
    function Pie(){
        this.imgY = 751;              
        this.imgW = 52;              
        this.imgH = 420;
        this.canX = viewSize.width;               //默认在画布的最右边
        this.canW = Math.ceil(80 / 450 * viewSize.width);
        this.canH = Math.ceil(this.canW * 420 / 52);
    }
    //其中top我们随机生成,代表的是同一组水管中,上水管的左下角在画布中的y坐标
    //上水管类
    function UpPie(top){  
        Pie.call(this);                       //继承相同的属性
        this.imgX = 70;                       //上水管在原图中的x坐标  
        this.canY = top - this.canH;          //上水管在画布中的y坐标计算
        this.draw = drawPie;                   
    };
    //下水管类
    function DownPie(top){
        Pie.call(this);
        this.imgX = 0;
        this.canY = top + Math.ceil(150 / 800 * viewSize.height);  //上水管和下水管的距离固定,大小可调
        this.draw = drawPie;
    }
    function drawPie(){
        var speed = 2 * k;
        this.canX -= speed;  //每画一次就向左边走
        ctx.drawImage(img, this.imgX, this.imgY, this.imgW, this.imgH, 
                           this.canX, this.canY, this.canW, this.canH);
    }

    然后开始画水管:

    //用一个数组存在画面中的水管
    var Pies = [];
    //创建水管函数,首先随机生成top,然后分别实例化上、下水管然后存进Pies里
    function createPie(){
        var minTop = Math.ceil(90 /800 * viewSize.height),
            maxTop = Math.ceil(390 /800 * viewSize.height),
            top = minTop + Math.ceil(Math.random() * (maxTop - minTop));
        Pies.push(new UpPie(top));
        Pies.push(new DownPie(top));
    };
    //画水管时,首先判断
    //第一组水管出左边屏幕,移除水管
    if(Pies[0].canX <= -Pies[0].canW && Pies.length == 4){
        Pies[0] = null;
        Pies[1] = null;
        Pies.shift();
        Pies.shift();
        canCount = true;
    }
    //第一组水管到达中间时创建水管
    if(Pies[0].canX <= 0.5 * (viewSize.width - Pies[0].canW) && Pies.length == 2){
        createPie();
    }
    //然后就可以画水管
    for(var i = 0, len = Pies.length; i < len; i++){
        Pies[i].draw();
    }

    4)画分数,比较简单,主要是需要计算居中:

    /**
     * 分数类
     */
    function Score(){
        this.imgX = 900;
        this.imgY = 400;
        this.imgW = 36;
        this.imgH = 54;
        this.canW = Math.ceil(36 * k);
        this.canH = Math.ceil(54 * k);
        this.canY = Math.ceil(50 / 800 * viewSize.height);
        this.canX = Math.ceil(viewSize.width / 2 - this.canW / 2);
        this.score = 0;
    }
    Score.prototype.draw = function(){
        var aScore = ('' + this.score).split('');
        var len = aScore.length;
        //计算一下居中
        this.canX = 0.5 * (viewSize.width - (this.canW + 10) * len + 10);
        for(var i = 0; i < len; i++){
            var num = parseInt(aScore[i]);
            if(num < 5){
                var imgX = this.imgX + num * 40;
                var imgY = 400;
            }else{
                var imgX = this.imgX + (num - 5) * 40;
                var imgY = 460;
            }
            var canX = this.canX + i * (this.canW + 2);
            ctx.drawImage(img, imgX, imgY, this.imgW, this.imgH, canX, this.canY, this.canW, this.canH);
        }
    };

    然后画就简单了

    var score = new Score();
    
    score.draw();

    5)画地板,主要是需要让它向左移动

    //地板类
    function Ground(){
        this.imgX = 0;
        this.imgY = 600;
        this.imgH = 112;
        this.imgW = 600;
        this.canH = Math.ceil(112 * k);
        this.canW = Math.ceil(k * 800);
        this.canX = 0;
        this.canY = viewSize.height - this.canH;
    }
    Ground.prototype.draw = function(){
        if(this.imgX > 24) this.imgX = 0;   //因为无限滚动,所以需要无痕接上
        ctx.drawImage(img, this.imgX, this.imgY, this.imgW, this.imgH, 
                           this.canX, this.canY, this.canW, this.canH);
        this.imgX += 2;
    };

    画的时候实例就可以了:

    var ground = new Ground();
    
    ground.draw();

    到这里你就可以看到水管和地板可以向后走了,小鸟也能飞起来了,只是会不断下落,所以我们要设置点击弹跳。

    第三步:点击处理

    //touchstart是手机端,mousedown是PC端
    document.ontouchstart = document.onmousedown = function(e){
        //游戏如果结束点击无效
        if(gameover) return;
        if(isStarted){
            //游戏如果开始了,那么久开始
            //刚才在小鸟飞出顶部我做了点击屏蔽,
            if(canClick){
                //当我们点击的时候,我们应该恢复初始状态,初始状态就是this.t=0, bird.y[i]储存了初始高度
                for(var i = 0; i < 3; i++){
                    bird.y[i] = bird.canY[i];
                }
                bird.t = 0;
            }else{
                return;
            }
        }else{
            //游戏没有开始说明在准备,所以开始
            isStarted = true;
        }
    
        //在ios客户端,touch事件之后还会触发click事件,阻止默认事件就可以屏蔽了
        var e = e || window.event;
        if(e.preventDefault){
            e.preventDefault();
        }else{
            e.returnValue = false;
        }
    };

    现在你已经可以使小鸟跳跃了,胜利就在前方。

    第四步:check函数

    检测小鸟和地板是否碰撞最为简单:

    //地板碰撞,小鸟的y坐标 + 小鸟的高度 >= 地板的y坐标,表示撞了地板 
    if(bird.canY[0] + bird.canH[0] >= ground.canY){
        gameover = true;
        return;
    }

    检测小鸟和水管是否碰撞,可以化成两个矩形是否重合,重合的情况比较复杂,我们可以看不重合的情况:只有4种,如图:

    (1)这里写图片描述 (2) 这里写图片描述

    (3)这里写图片描述 (4)这里写图片描述

    只要符合上面一种情况就不重合,其余情况就是重合,所以:

    //检测两个矩形是否重合,可以反着看,先找出矩形不重合的情况,
    function isOverLay(r1, r2){
        var flag = false;
        if(r1.top > r2.bottom || r1.bottom < r2.top || r1.right < re2.left || r1.left > r2.right){
            flag = true;
        }
        //反之就是重合
        return !flag;
    }
    //水管碰撞
    var birdRect = {
        top: bird.canY[0],
        bottom: bird.canY[0] + bird.canH[0],
        left: bird.canX[0],
        right: bird.canX[0] + bird.canW[0]
    };
    for(var i = 0, len = Pies.length; i < len; i++){
        var t = Pies[i];
        var pieRect = {
            top: t.canY,
            bottom: t.canY + t.canH,
            left: t.canX,
            right: t.canX + t.canW
        };
        if(isOverLay(birdRect,pieRect)){
            gameover = true;
            return;
        }
    }

    还需要检查是否得分

    if(Math.floor(bird.canX[0]) > Math.floor(Pies[0].canX + Pies[0].canW) && canCount){
        //小鸟的左边出了第一组水管的右边就得分,得分以后,第一组水管还没出屏幕左边时不能计算得分
        canCount = false;
        score.score++;
    };

    所以check函数为:

    function check(){
        function isOverLay(r1, r2){
            var flag = false;
            if(r1.top > r2.bottom || r1.bottom < r2.top || r1.right < re2.left || r1.left > r2.right){
                flag = true;
            }
            //反之就是重合
            return !flag;
        }
        //地板碰撞
        if(bird.canY[0] + bird.canH[0] >= ground.canY){
            console.log(viewSize)
            console.log(bird.canY[0],bird.canH[0],ground.canY)
            gameover = true;
            return;
        }
        //水管碰撞
        var birdRect = {
            top: bird.canY[0],
            bottom: bird.canY[0] + bird.canH[0],
            left: bird.canX[0],
            right: bird.canX[0] + bird.canW[0]
        };
        for(var i = 0, len = Pies.length; i < len; i++){
            var t = Pies[i];
            var pieRect = {
                top: t.canY,
                bottom: t.canY + t.canH,
                left: t.canX,
                right: t.canX + t.canW
            };
            if(isOverLay(birdRect,pieRect)){
                gameover = true;
                return;
            }
        }
        //是否得分
        if(Math.floor(bird.canX[0]) > Math.floor(Pies[0].canX + Pies[0].canW) && canCount){
            canCount = false;
            score.score++;
        };
    }

    现在游戏已经可以玩了,就是还差gameover处理,和重新开始处理了

    第五步:gameover处理:

    //画gameover字样
    ctx.drawImage(img, 170, 990, 300, 90, Math.ceil(viewSize.width * 0.5 - k * 277 * 0.5), 
                  Math.ceil(200 / 800 * viewSize.height), 277 * k, 75 * k);
    //画重新开始点击按钮           
    ctx.drawImage(img, 550, 1005, 160, 90, Math.ceil(viewSize.width * 0.5 - k * 160 * 0.5), 
                  Math.ceil(400 / 800 * viewSize.height), 160 * k, 90 * k)
    //因为有重新点击开始,所以在html中有个隐藏的div用来点击重新开始,现在让它出现
    startBtn.style.display = 'block';
    startBtn.style.width = 160 * k + 'px';
    startBtn.style.height = 90 * k + 'px';
    startBtn.style.left = Math.ceil(viewSize.width * 0.5 - k * 160 * 0.5) + 'px';
    startBtn.style.top = Math.ceil(400 / 800 * viewSize.height) + 'px';
    //消除定时器       
    cancelAnimationFrame(timer);  //如果用setTimeout就是:cleatTimeout(timer)
    //回收资源
    ground = null;
    bird = null;
    score = null;
    for(var i = 0, len = Pies.length; i < len; i++){
        Pies[i] = null;
    }
    Pies = [];

    第六步:重新开始游戏处理

    startBtn.ontouchstart = startBtn.onmousedown = function(e){
        //初始化参数
        canClick = true;
        gameover = false;
        canCount = true;
        isStarted = false;
        startBtn.style.display = 'none';
        ground = new Ground();
        bird = new Bird();
        score = new Score();
        Pies = [];
        createPie();
        //开定时器
        timer = requestAnimationFrame(start); //或者timer = setTimeout(start, 16);
        //阻止冒泡到document
        var e = e || window.event;
        if(e.stopPropagation){
            e.stopPropagation();
        }else{
            e.cancelBubble = false;
        }
    
    }

    到此结束,贴上全部代码,有耐心看完的估计没有几个,哈哈哈

    html代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Flappy Bird</title>
        <meta name="viewport" content="width=device-width"/>
        <style>
           body,html{
               padding:0;
               margin:0;
               height:100%;
               width:100%;
               backgroung:#f1f1f1;
               cursor:pointer;
               overflow: hidden;
            }
            canvas{
                position:relative;
                z-index:998;
            }
            #restart{
                position:absolute;
                top:0;left:0;
                z-index:999;
                display:none;
            }
        </style>
    </head>
    <body>
        <canvas id="canvas"></canvas>
        <div id="restart"></div>
        <script src="index.js"></script>
    </body>
    </html>

    js代码

    var viewSize = (function(){
    
        var pageWidth = window.innerWidth,
            pageHeight = window.innerHeight;
    
        if (typeof pageWidth != 'number') {
            if (document.compatMode == 'CSS1Compat') {
                pageHeight = document.documentElement.clientHeight;
                pageWidth = document.documentElement.clientWidth;
            } else {
                pageHeight = document.body.clientHeight;
                pageWidth = document.body.clientWidth;
            }
        };
        if(pageWidth >= pageHeight){
            pageWidth = pageHeight * 360 / 640;
        }
        pageWidth = pageWidth >  414 ? 414 : pageWidth;
        pageHeight = pageHeight > 736 ? 736 : pageHeight;
    
        return {
             pageWidth,
            height: pageHeight
        };
    
    })();
    
    (function(){
        var lastTime = 0;
        var prefixes = 'webkit moz ms o'.split(' '); //各浏览器前缀
    
        var requestAnimationFrame = window.requestAnimationFrame;
        var cancelAnimationFrame = window.cancelAnimationFrame;
    
        var prefix;
    //通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式
        for( var i = 0; i < prefixes.length; i++ ) {
            if ( requestAnimationFrame && cancelAnimationFrame ) {
                break;
            }
            prefix = prefixes[i];
            requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ];
            cancelAnimationFrame  = cancelAnimationFrame  || window[ prefix + 'CancelAnimationFrame' ] || window[ prefix + 'CancelRequestAnimationFrame' ];
        }
    
    //如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout
        if ( !requestAnimationFrame || !cancelAnimationFrame ) {
            requestAnimationFrame = function( callback, element ) {
                var currTime = new Date().getTime();
                //为了使setTimteout的尽可能的接近每秒60帧的效果
                var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
                var id = window.setTimeout( function() {
                    callback( currTime + timeToCall );
                }, timeToCall );
                lastTime = currTime + timeToCall;
                return id;
            };
    
            cancelAnimationFrame = function( id ) {
                window.clearTimeout( id );
            };
        }
    
    //得到兼容各浏览器的API
        window.requestAnimationFrame = requestAnimationFrame;
        window.cancelAnimationFrame = cancelAnimationFrame;
    })()
    
    var canvas = document.getElementById('canvas'),
        ctx = canvas.getContext('2d'),
        img = new Image(),
        k= viewSize.height / 600,
        canClick,
        gameover,
        canCount,
        isStarted,
        timer,
        ground,
        bird,
        score,
        Pies,
        startBtn = document.getElementById('restart');
    //导入图像
    img.onload = start;
    img.src = './img.png';
    //设置画布宽高
    canvas.width = viewSize.width;
    canvas.height = viewSize.height;
    init();
    function init(){
        canClick = true;
        gameover = false;
        canCount = true;
        isStarted = false;
        startBtn.style.display = 'none';
        ground = new Ground();
        bird = new Bird();
        score = new Score();
        Pies = [];
        createPie();
    }
    function destroy(){
        ground = null;
        bird = null;
        score = null;
        for(var i = 0, len = Pies.length; i < len; i++){
            Pies[i] = null;
        }
        Pies = [];
    }
    /**
     * 开始游戏
     */
    function start(){
        check();
        if(gameover){
            console.log(1)
            ctx.drawImage(img, 170, 990, 300, 90, Math.ceil(viewSize.width * 0.5 - k * 277 * 0.5), Math.ceil(200 / 800 * viewSize.height), 277 * k, 75 * k)
            ctx.drawImage(img, 550, 1005, 160, 90, Math.ceil(viewSize.width * 0.5 - k * 160 * 0.5), Math.ceil(400 / 800 * viewSize.height), 160 * k, 90 * k)
            startBtn.style.width = 160 * k + 'px';
            startBtn.style.height = 90 * k + 'px';
            startBtn.style.left = Math.ceil(viewSize.width * 0.5 - k * 160 * 0.5) + 'px';
            startBtn.style.top = Math.ceil(400 / 800 * viewSize.height) + 'px';
            startBtn.style.display = 'block';
            cancelAnimationFrame(timer);
            destroy();
        }else{
            //清除
            ctx.clearRect(0,0,viewSize.width,viewSize.height);
            //画背景
            ctx.drawImage(img, 0, 0, 800, 600, 0, 0, Math.ceil(k * 800), viewSize.height);
            if(isStarted){
                //第一组水管出左边屏幕,移除水管
                if(Pies[0].canX <= -Pies[0].canW && Pies.length == 4){
                    Pies[0] = null;
                    Pies[1] = null;
                    Pies.shift();
                    Pies.shift();
                    canCount = true;
                }
                //画小鸟
                bird.draw();
                //创建水管
                if(Pies[0].canX <= 0.5 * (viewSize.width - Pies[0].canW) && Pies.length == 2){
                    createPie();
                }
                //画水管
                for(var i = 0, len = Pies.length; i < len; i++){
                    Pies[i].draw();
                }
    
            }else{
                //画ready
                ctx.drawImage(img, 170, 900, 300, 90, Math.ceil(viewSize.width * 0.5 - k * 277 * 0.5), Math.ceil(200 / 800 * viewSize.height), 277 * k, 75 * k)
                ctx.drawImage(img, 170, 1150, 230, 150, Math.ceil(viewSize.width * 0.5 - k * 200 * 0.5), Math.ceil(400 / 800 * viewSize.height), 200 * k, 150 * k)
            }
            //画分数
            score.draw();
            //画地板
            ground.draw();
            //设置定时器
            timer = requestAnimationFrame(start);
    
        }
    
    };
    /**
     * 检查是否碰撞、得分
     */
    function check(){
        function isOverLay(rect1, rect2){
            var flag = false;
            if(rect1.top > rect2.bottom || rect1.bottom < rect2.top || rect1.right < rect2.left || rect1.left > rect2.right) flag = true;
            return !flag;
        }
        //地板碰撞
        if(bird.canY[0] + bird.canH[0] >= ground.canY){
            console.log(viewSize)
            console.log(bird.canY[0],bird.canH[0],ground.canY)
            gameover = true;
            return;
        }
        //水管碰撞
        var birdRect = {
            top: bird.canY[0],
            bottom: bird.canY[0] + bird.canH[0],
            left: bird.canX[0],
            right: bird.canX[0] + bird.canW[0]
        };
        for(var i = 0, len = Pies.length; i < len; i++){
            var t = Pies[i];
            var pieRect = {
                top: t.canY,
                bottom: t.canY + t.canH,
                left: t.canX,
                right: t.canX + t.canW
            };
            if(isOverLay(birdRect,pieRect)){
                gameover = true;
                return;
            }
        }
        //是否得分
        if(Math.floor(bird.canX[0]) > Math.floor(Pies[0].canX + Pies[0].canW) && canCount){
            canCount = false;
            score.score++;
        };
    }
    /**
     * 点击
     */
    document.ontouchstart = document.onmousedown = function(e){
        if(gameover) return;
        if(isStarted){
            if(canClick){
                for(var i = 0; i < 3; i++){
                    bird.y[i] = bird.canY[i];
                }
                bird.t = 0;
            }else{
                return;
            }
        }else{
            isStarted = true;
        }
        var e = e || window.event;
        if(e.preventDefault){
            e.preventDefault();
        }else{
            e.returnValue = false;
        }
    };
    
    startBtn.ontouchstart = startBtn.onmousedown = function(e){
        var e = e || window.event;
        if(e.stopPropagation){
            e.stopPropagation();
        }else{
            e.cancelBubble = false;
        }
        init();
        timer = requestAnimationFrame(start);
    }
    /**
     * 分数类
     */
    function Score(){
        this.imgX = 900;
        this.imgY = 400;
        this.imgW = 36;
        this.imgH = 54;
        this.canW = Math.ceil(36 * k);
        this.canH = Math.ceil(54 * k);
        this.canY = Math.ceil(50 / 800 * viewSize.height);
        this.canX = Math.ceil(viewSize.width / 2 - this.canW / 2);
        this.score = 0;
    }
    Score.prototype.draw = function(){
        var aScore = ('' + this.score).split('');
        var len = aScore.length;
        this.canX = 0.5 * (viewSize.width - (this.canW + 10) * len + 10);
        for(var i = 0; i < len; i++){
            var num = parseInt(aScore[i]);
            if(num < 5){
                var imgX = this.imgX + num * 40;
                var imgY = 400;
            }else{
                var imgX = this.imgX + (num - 5) * 40;
                var imgY = 460;
            }
            var canX = this.canX + i * (this.canW + 2);
            ctx.drawImage(img, imgX, imgY, this.imgW, this.imgH, canX, this.canY, this.canW, this.canH);
        }
    };
    /**
     * 小鸟类
     */
    function Bird(){
        this.imgX = [170, 222, 275];
        this.imgY = [750, 750, 750];
        this.imgW = [34, 34, 34];
        this.imgH = [24, 24, 24];
        this.index = 2;
        this.count = 0;
        this.step = 1;
        var canX = Math.ceil(110 / 450 * viewSize.width);
        this.canX = [canX, canX, canX];
        var canY = Math.ceil(380 / 800 * viewSize.height);
        this.canY = [canY, canY, canY];
        var canW = Math.ceil(34 * k);
        this.canW = [canW, canW, canW];
        var canH = Math.ceil(24 * k);
        this.canH = [canH, canH, canH];
        this.t = 0;
        this.y = [canY, canY, canY];
    }
    Bird.prototype.draw = function(){
        var index = this.index;
        //翅膀拍动
        this.count++;
        if(this.count == 6){
            this.index += this.step;
            this.count = 0;
        }
        if((this.index == 2 && this.step == 1) || this.index == 0 && this.step == -1) this.step = - this.step;
        //计算垂直位移,使用公式 y = a * t * (t - c)
        var c = 0.7 * 60;
        var minY = - 85 * viewSize.height / 800;
        var a = -minY * 4 / (c * c);
        var dy = a * this.t * (this.t - c);
    
        if(this.y[0] + dy < 0){
            canClick = false;
        }else{
            canClick = true;
        }
        for(var i = 0; i < 3; i++){
            this.canY[i] = this.y[i] + Math.ceil(dy);
        }
        this.t++;
        ctx.drawImage(img, this.imgX[index], this.imgY[index], this.imgW[index], this.imgH[index], this.canX[index], this.canY[index], this.canW[index], this.canH[index])
    
    };
    /**
     * 水管基类
     */
    function Pie(){
        this.imgY = 751;
        this.imgW = 52;
        this.imgH = 420;
        this.canX = viewSize.width;
        this.canW = Math.ceil(80 / 450 * viewSize.width);
        this.canH = Math.ceil(this.canW * 420 / 52);
    }
    /**
     * 上水管类
     */
    function UpPie(top){
        Pie.call(this);
        this.imgX = 70;
        this.canY = top - this.canH;
        this.draw = drawPie;
    };
    UpPie.prototype = new Pie();
    /**
     * 下水管类
     */
    function DownPie(top){
        Pie.call(this);
        this.imgX = 0;
        this.canY = top + Math.ceil(150 / 800 * viewSize.height);
        this.draw = drawPie;
    }
    DownPie.prototype = new Pie();
    
    function drawPie(){
        var speed = 2 * k;
        this.canX -= speed;
        ctx.drawImage(img, this.imgX, this.imgY, this.imgW, this.imgH, this.canX, this.canY, this.canW, this.canH);
    }
    
    /**
     * 创建水管
     */
    function createPie(){
        var minTop = Math.ceil(90 /800 * viewSize.height),
            maxTop = Math.ceil(390 /800 * viewSize.height),
            top = minTop + Math.ceil(Math.random() * (maxTop - minTop));
        Pies.push(new UpPie(top));
        Pies.push(new DownPie(top));
    };
    /**
     * 地板类
     */
    function Ground(){
        this.imgX = 0;
        this.imgY = 600;
        this.imgH = 112;
        this.imgW = 600;
        this.canH = Math.ceil(112 * k);
        this.canW = Math.ceil(k * 800);
        this.canX = 0;
        this.canY = viewSize.height - this.canH;
    }
    Ground.prototype.draw = function(){
        if(this.imgX > 24) this.imgX = 0;
        ctx.drawImage(img, this.imgX, this.imgY, this.imgW, this.imgH, this.canX, this.canY, this.canW, this.canH);
        this.imgX += 2;
    };
  • 相关阅读:
    spring注解实现业务层事务管理,当业务层自调用时,事务失效问题解决
    spring的事务
    maven创建web项目
    eclipse安装最新版svn
    .ncx文件剖析
    关闭MongoDB服务的几种方法
    mongodb添加验证用户 删除用户
    高性能kv存储之Redis、Redis Cluster、Pika:如何应对4000亿的日访问量?
    Python中msgpack库的使用
    彻底理解一致性哈希算法(consistent hashing)
  • 原文地址:https://www.cnblogs.com/kang-xjtu/p/5251457.html
Copyright © 2020-2023  润新知