Flappy Bird项目代码

学了一段时间的Canvas 有必要实战一下,这是一套视频课程中的项目,拿来练练手,毕竟编程只看不写等于不会。就好像学汉字,总共常用的也就那几千个,每个字也都认识,学霸们能够写出大作,学渣们却连一篇像样的作文都写不出来。所以,最主要的是多写,多练,借鉴别人的代码结构,如何巧妙使用,如何构造,自己能写出来,能理解,能运用才算自己。长点心,没有什么学不会。共勉,以上都是说给自己的。 .~_~.

先看下什么是Flappy Bird:Flappy Bird百度百科 , 好像还有个floppy bird ,大概意思就是无精打采的小鸟。先玩两局感受下。

先实现背景的飞翔
Flappy Bird项目代码_第1张图片


> 1.仅绘制天空

<body>
    
    <canvas id="cvs"> canvas>
    <script src="./loadImage.js">
    script>
    <script>
    var cvs = document.querySelector('#cvs');
    var ctx = cvs.getContext('2d');

    function Sky(ctx, img, step) {
        this.ctx = ctx;
        this.img = img;
        this.width = this.img.width;
        this.height = this.img.height;

        // 这里是绑定在类上的属性,那么由该类创建的实例共享该属性
        Sky.objNum++;

        // 该案例总共创建了两个实例,第一个放在画布上,第二个在第一个的右侧,宽高相等
        this.x = this.width * (Sky.objNum - 1);

        this.y = 0;
        // 移动的速度,每隔100ms移动20
        this.step = step || 20;
    }

    Sky.prototype = {
        constructor: Sky,
        draw: function() {
            this.ctx.drawImage(this.img, this.x, this.y);
        },
        update:function(){
            // 两个sky背景每隔一定时间100ms,同时往左移动,当第一个移出画布时(this.x <= -this.width),第一个的x坐标往右移两个宽度(注意,这里创建了两个sky实例)
            this.x -= this.step;
            if (this.x <= -this.width) {
                this.x += this.width *Sky.objNum;
            }
        }
    }

    Sky.objNum = 0;

    script>

    <script>
    loadImage({
        bird: './img/bird.png',
        land: './img/land.png',
        pipeDown: './img/pipeDown.png',
        pipeUp: './img/pipeUp.png',
        sky: './img/sky.png'
    }, function(imgObj) {
        // console.log(imgObj.bird.src);
        // console.log(imgObj.sky.src);

        // 画布的大小和背景图片的大小一样大
        cvs.width = imgObj.sky.width;
        cvs.height = imgObj.sky.height;

        var sky1 = new Sky(ctx, imgObj.sky);
        var sky2 = new Sky(ctx, imgObj.sky);

        setInterval(function() {
            sky1.draw();
            sky1.update();

            sky2.draw();
            sky2.update();
        }, 100);
    })
    script>
body>

2.天空中加入bird

这只鸟儿自动下落,点击画布,往上升一点。算法比较简单。

    <script>
    function Bird(ctx, img, widthFrame, heightFrame, x, y) {
        this.ctx = ctx;
        this.img = img;

        // 给的素材图片每一行有几个小图
        this.widthFrame = widthFrame;

        // 给的素材图片每一列有几个小图
        this.heightFrame = heightFrame;
        // 本次给的素材小鸟只有一行三张图片,我们是切换这三张图来动态显示小鸟,这个属性表示当前切换的第几个图
        this.currentFrame = 0;

        this.width = this.img.width / this.widthFrame;
        this.height = this.img.height / this.heightFrame;

        this.x = x;
        this.y = y;

        // 每次移动的步长
        this.moveDistance = 2;
        // 步长每次加0.5
        this.speedPlus = 0.5;
        // 点击画布,步长变为负值,表示向上移动
        this._bind();

    }
    Bird.prototype = {
        constructor: Bird,
        draw: function() {
            this.ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
            // this.ctx.drawImage(this.img, this.width * this.currentFrame, 0, this.width, this.height, this.x , this.y, this.width, this.height);
        },
        update: function() {
            this.currentFrame = ++this.currentFrame >= this.widthFrame ? 0 : this.currentFrame;

            // this.x += this.speedPlus;
            // 让小鸟不断下落
            // setInterval中,每隔一定时间(100ms)向下移动
            this.y += this.moveDistance;
            // 给速度一个增量,加速度为speedPlus
            this.moveDistance += this.speedPlus;

        },
        _bind: function() {
            var self = this;
            this.ctx.canvas.addEventListener('click', function() {
                // 点击一下屏幕,小鸟的 每次移动距离变为-4 表示向上移动,但是由于(this.moveDistance += this.speedPlus;)这个值会由 -4 慢慢变大 ,小鸟先上升后下降
                self.moveDistance = -4;
            });
        }
    }
    script>
    <script>
    loadImage({
        bird: './img/bird.png',
        land: './img/land.png',
        pipeDown: './img/pipeDown.png',
        pipeUp: './img/pipeUp.png',
        sky: './img/sky.png'
    }, function(imgObj) {
        // console.log(imgObj.bird.src);
        // console.log(imgObj.sky.src);

        // 画布的大小和背景图片的大小一样大
        cvs.width = imgObj.sky.width;
        cvs.height = imgObj.sky.height;

        var sky1 = new Sky(ctx, imgObj.sky);
        var sky2 = new Sky(ctx, imgObj.sky);

        var bird = new Bird(ctx, imgObj.bird, 3, 1, 10, 10);

        setInterval(function() {
            sky1.draw();
            sky1.update();

            sky2.draw();
            sky2.update();

            bird.draw();
            bird.update();

        }, 100);
    })
    script>

3.天空背景+鸟+大地

效果如下,这里可以点击屏幕使小鸟上天,但是录制这个gif的时候,屏幕点击不了,所以就没有理会小鸟,只看下面的大地的左右移动,跟天空类似。

Flappy Bird项目代码_第2张图片

    <script>
    function Bird(ctx, img, widthFrame, heightFrame, x, y) {
        this.ctx = ctx;
        this.img = img;

        // 给的素材图片每一行有几个小图
        this.widthFrame = widthFrame;

        // 给的素材图片每一列有几个小图
        this.heightFrame = heightFrame;
        // 本次给的素材小鸟只有一行三张图片,我们是切换这三张图来动态显示小鸟,这个属性表示当前切换的第几个图
        this.currentFrame = 0;

        this.width = this.img.width / this.widthFrame;
        this.height = this.img.height / this.heightFrame;

        this.x = x;
        this.y = y;

        // 每次移动的步长
        this.moveDistance = 2;
        // 步长每次加0.5
        this.speedPlus = 0.5;
        // 点击画布,步长变为负值,表示向上移动
        this._bind();

    }
    Bird.prototype = {
        constructor: Bird,
        draw: function() {
            this.ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
            // this.ctx.drawImage(this.img, this.width * this.currentFrame, 0, this.width, this.height, this.x , this.y, this.width, this.height);
        },
        update: function() {
            this.currentFrame = ++this.currentFrame >= this.widthFrame ? 0 : this.currentFrame;

            // this.x += this.speedPlus;
            // 让小鸟不断下落
            // setInterval中,每隔一定时间(100ms)向下移动
            this.y += this.moveDistance;
            // 给速度一个增量,加速度为speedPlus
            this.moveDistance += this.speedPlus;

        },
        _bind: function() {
            var self = this;
            this.ctx.canvas.addEventListener('click', function() {
                // 点击一下屏幕,小鸟的 每次移动距离变为-4 表示向上移动,但是由于(this.moveDistance += this.speedPlus;)这个值会由 -4 慢慢变大 ,小鸟先上升后下降
                self.moveDistance = -4;
            });
        }
    }
    script>
    <script>
    // 这里用混入继承来实现扩展原型
    function extend(o1, o2) {
        for (var key in o2) {
            if (o2.hasOwnProperty(key)) {
                o1[key] = o2[key];
            }
        }
    }

    function Land(ctx, img, speed) {
        this.ctx = ctx;
        this.img = img;
        this.x = Land.objNum * this.img.width;
        // 大地在下方,大地的顶部在画布的高度-大地图片的高度处
        this.y = this.ctx.canvas.height - this.img.height;
        this.speed = speed || 10;
        Land.objNum++;
    }

    extend(Land.prototype, {
        draw: function() {
            this.ctx.drawImage(this.img, this.x,this.y);
        },
        update: function() {
            this.x -= this.speed;
            if (this.x < -this.img.width) {
                // 道理同天空背景的移动,只不过这里用了四个大地,而Sky我们实例化了两个
                this.x += Land.objNum * this.img.width;
            }
        }
    });
    Land.objNum = 0;
    script>
    <script>
    loadImage({
        bird: './img/bird.png',
        land: './img/land.png',
        pipeDown: './img/pipeDown.png',
        pipeUp: './img/pipeUp.png',
        sky: './img/sky.png'
    }, function(imgObj) {
        // console.log(imgObj.bird.src);
        // console.log(imgObj.sky.src);

        // 画布的大小和背景图片的大小一样大
        cvs.width = imgObj.sky.width;
        cvs.height = imgObj.sky.height;

        var sky1 = new Sky(ctx, imgObj.sky);
        var sky2 = new Sky(ctx, imgObj.sky);

// 实例化四个大地,三个不够,可以画个图就知道了
        var land = new Land(ctx, imgObj.land, 10);
        var land1 = new Land(ctx, imgObj.land, 10);
        var land2 = new Land(ctx, imgObj.land, 10);
        var land3 = new Land(ctx, imgObj.land, 10);

        var bird = new Bird(ctx, imgObj.bird, 3, 1, 10, 10);



        setInterval(function() {
            sky1.draw();
            sky1.update();

            sky2.draw();
            sky2.update();

            land.draw();
            land.update();
            land1.draw();
            land1.update();
            land2.draw();
            land2.update();
            land3.draw();
            land3.update();



            bird.draw();
            bird.update();

        }, 100);
    })
    script>

4.Sky + Bird + Land +Pipe

   


            // 让背景动起来
            var timer = setInterval(function() {

                /*
                * 每次绘制新的游戏画面时,
                * 先判断小鸟有没有碰撞,
                * 如果碰撞暂停定时器。
                * */
                var birdCoreX = bird.x + bird.width / 2;
                var birdCoreY = bird.y + bird.height / 2;

                // 如果小鸟撞向管道,或者上天,或者入地,那么游戏结束
                if ( ctx.isPointInPath( birdCoreX, birdCoreY )
                        || birdCoreY < 0
                        || birdCoreY > (ctx.canvas.height - imgObj.land.height) )
                {
                    clearInterval( timer ); //停止游戏
                    ctx.fillStyle = 'rgba( 100, 100, 100, 0.8 )';
                    ctx.fillRect( 0, 0, ctx.canvas.width, ctx.canvas.height );
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';

                    ctx.fillStyle = 'red';
                    ctx.font = '900 40px 微软雅黑';
                    ctx.fillText( 'GAME OVER!!!', ctx.canvas.width / 2, ctx.canvas.height / 2 );
                    return;
                }

                // 先清除上一次绘制的6个管道路径,
                // 然后再按照新的位置绘制新路径
                ctx.beginPath();

                pipe.draw();
                pipe.update();

                pipe1.draw();
                pipe1.update();

                pipe2.draw();
                pipe2.update();

                pipe3.draw();
                pipe3.update();

                pipe4.draw();
                pipe4.update();

                pipe5.draw();
                pipe5.update();
            }, 50);

5.ctx.save()和ctx.restore是什么意思呢?

这里写图片描述

我们先看下上图的代码:

    
当你屏蔽掉ctx.save()和ctx.restore()之后,会发现根本不是想要的样子,至于理解的话,[引用别人写出来:](http://bbs.csdn.net/topics/390866099)

❑ save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。

❑ restore:用来恢复Canvas之前保存的状态。防止save后对Canvas执行的操作对后续的绘制有影响。

save和restore要配对使用(restore可以比save少,但不能多),如果restore调用次数比save多,会引发Error。

下面分别列出有无save——restore的情况:
**有save——restore**
i=1
执行save保存起点(0,0),图中红色小点,
执行ctx.translate(),起点变为(60*i,0)即(60,0),第一个黑点,
执行drawTop():起点偏移到(x + radius*cos( startAngle), y + radius*sin( startAngle))即(90,0)
执行restroe起点回到(0,0),图中红色小点。
i=2
执行save保存起点(0,0)
执行ctx.translate(),起点变为(60*i,0)即(120,0),第二个黑点,
执行drawTop():起点偏移到(x + radius*cos( startAngle), y + radius*sin( startAngle))即(150,0)
执行restroe起点回到(0,0)
i=3
执行save保存起点(0,0)
执行ctx.translate(),起点变为(60*i,0)即(180,0)
执行drawTop():起点偏移到(x + radius*cos( startAngle), y + radius*sin( startAngle))即(210,0)
执行restroe起点回到(0,0)
看到坐标点的变化了吗?你画的半圆直径是60,所以第一个半圆的起止点在(60,0)和(120,0)第二个半圆的起止点在(120,0)和(180,0),第三个半圆的起止点(180,0)和(240,0)所以这些半圆都是相切的,就像下面那个图一样。


**无save——restore**
i=1
执行ctx.translate(),起点变为(60*i,0)即(60,0)
执行drawTop():起点偏移到(x + radius*cos( startAngle), y + radius*sin( startAngle))即(90,0)
i=2
执行ctx.translate(),起点变为(90+60*i,0)即(210,0)
执行drawTop():起点偏移到(x + radius*cos( startAngle), y + radius*sin( startAngle))即(240,0)
i=3
执行ctx.translate(),起点变为(240+60*i,0)即(420,0)
执行drawTop():起点偏移到(x + radius*cos( startAngle), y + radius*sin( startAngle))即(450,0)
对比坐标,第一个半圆起止点是(60,0)和(120,0),第二个半圆起止点是(210,0)和(270,0),第三个半圆的起止点是(420,0)和(480,0)。看到坐标了吗?第一个半圆的止点与第二个半圆的起点差了90,第二个半圆的止点与第三个半圆的起点差了150,所以你画出的图像就错位了,就像你的上图一样

这里写图片描述

这里写图片描述

这就很容易理解了。相当于,笔触每次被挪动后,放回原位,下次别人来找的时候,在原来的地方能找到,而不是去另外的地方找。

你可能感兴趣的:(原创)