当前位置: 首页 > 工具软件 > 1w-flappy > 使用案例 >

canvas-flappy bird

冀阳文
2023-12-01

一、利用中介者模式进行业务搭建

  • Game是游戏中介者类,它的作用就是中介者的作用
  • Bird小鸟类,作用是对小鸟的业务进行封装
  • Land大地类,封装地面的样式
  • Background背景类,作用是封装背景图
  • PiPe管子类,作用是随机出现管子

辅助插件 underscore.js

图片传个资源包吧

二、Game类

(function () {
    window.Game = function () {
        //获取画布设置上下文
        this.canvas = document.querySelector("canvas");
        this.ctx = this.canvas.getContext("2d");
        //维护资源
        this.R = {
            "bg_day" : "images/bg_day.png",
            "land" : "images/land.png",
            "pipeDown" : "images/pipe_up.png",
            "pipeUp" : "images/pipe_down.png",
            "bird0_0" : "images/bird0_0.png",
            "bird0_1" : "images/bird0_1.png",
            "bird0_2" : "images/bird0_2.png",
            "bg_night" : "images/bg_night.png",
            "title" : "images/title.png",
            "button" : "images/button.png",
            "tutorial" : "images/tutorial.png",
            "0" : "images/font_048.png",
            "1" : "images/font_049.png",
            "2" : "images/font_050.png",
            "3" : "images/font_051.png",
            "4" : "images/font_052.png",
            "5" : "images/font_053.png",
            "6" : "images/font_054.png",
            "7" : "images/font_055.png",
            "8" : "images/font_056.png",
            "9" : "images/font_057.png"
            
        }
        //维护类的数组
        this.types = [];
        //伟华一个管子类
        this.pipes = [];
        //图片总数
        this.allAmout = _.keys(this.R).length;
        //维护一个对象,值是img对象,和this.R 一样有同样的k
        this.Robj = {}
        //加载资源
        this.loadResource();
        //帧编号
        this.frame = 0;
        //绑定点击事件
        //this.bindEvent();
        //初始化场景设置
        this.sceneNum = 0;
        //加分
        this.score = 0;
    }
    //加载图片
    Game.prototype.loadResource = function () {
        //已加载资源数量
        var already = 0;
        for(var k in this.R){
            //创建img
            this.Robj[k] = new Image();
            //添加src属性
            this.Robj[k].src = this.R[k];
            //备份上下文
            var self = this;
            this.Robj[k].onload = function () {
                //擦除画布
                self.ctx.clearRect(0,0,self.canvas.width,self.canvas.height);
                already ++ ;
                //渲染图片的加载状态
                self.ctx.font = "20px 微软雅黑";
                self.ctx.fillText("已加载"+already+"/"+self.allAmout+"张",20,40);
                //判断是否加载完
                if(already == self.allAmout){
                    //alert("加载完了")
                    self.start();
                }
            }
           // console.log(this.Robj[k])
        }
    }
    Game.prototype.start = function () {
        //创建场景管理器
        this.scene = new SceneManager();
        //初始的场景状态
        this.scene.enter(this.sceneNum);
        var self = this;
        game.timer = setInterval(function(){
            self.scene.render();
        },20)
        /*var self = this;
        //背景
        this.bg = new Background();
        //创建大地
        this.land = new Land();
        //小鸟
        this.bird = new Bird();
        setInterval(() => {
             //擦除画布
             self.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)
            //设置游戏主循环
            self.loop();
        }, 10);*/
    }
    //游戏的主循环
    Game.prototype.loop = function () {

       /* _.each(this.types,function (type) {
            type.update();
            type.render();
        })
        this.frame ++;
        if(this.frame % 70 == 0){
            this.pipes.push(new Pipe());
        }
        //帧编号
        this.ctx.font = "14px 微软雅黑";
        this.ctx.fillText("帧编号" +this.frame,10,20)*/
    }
    Game.prototype.bindEvent = function () {
        var self = this;
        this.canvas.onclick = function () {
            self.bird.fly()
        }
    }
    Game.prototype.addScore = function () {
        var ge = this.score % 10;
        var shi = parseInt( this.score / 10);
        console.log(ge,shi)
        this.ctx.drawImage(this.Robj[ge],this.canvas.width/2,0,24,44)
        this.ctx.drawImage(this.Robj[shi],this.canvas.width/2-30,0,24,44)
    }
})()

三、Background类

(function () {
    window.Background = function () {
        game.types.push(this);
        //背景图
        this.image = game.Robj["bg_day"];
        //初始位置
        this.x = 0;
    }
    Background.prototype.update = function () {
        //移动
        this.x -=1;
        //如果图片走出屏幕  归位
        if(this.x < -game.canvas.width){
            this.x = 0;
        }
    }
    Background.prototype.render = function () {
        //console.log(this.image)
        game.ctx.drawImage(this.image,this.x,0,game.canvas.width,game.canvas.height);
        //设置假图避免露白
        game.ctx.drawImage(this.image,game.canvas.width+this.x,0,game.canvas.width,game.canvas.height);
    }
})()

四、Land类

(function () {
    window.Land = function () {
        //类放到 game维护类的数组中
        game.types.push(this);
        //设置图片
        this.image = game.Robj["land"];
        //初始位置
        this.x = 0;
    }

    Land.prototype.update = function () {
        this.x -= 3;
        if(this.x < -game.canvas.width){
            this.x = 0;
        }
    }
    Land.prototype.render = function () {
        //画图
        game.ctx.drawImage(this.image,this.x,game.canvas.height-112);
        //设置假图
        game.ctx.drawImage(this.image,game.canvas.width+this.x,game.canvas.height-112);
    }
})()

五、PiPe类

(function(){
    window.Pipe = function(){
        //将自己存放到game中
        game.types.push(this);
        //设置管子图片
        this.pipeDown = game.Robj["pipeUp"];
        this.pipeUp = game.Robj["pipeDown"];
        //随机数---上面管子的高度
        this.randomTopHeight = _.random(30,220);
        //两根管之间的距离
        this.space = 120;
        //下面管子的高度
        //canvas的高度-上面管子的高度-固定空间的高度-大地的高度;
        this.randomBottomHeight = game.canvas.height - this.randomTopHeight - this.space - 112;
        //下面管子的位置 top值上面的管子的高度+固定空间高度
        this.bottomPipePosition = this.randomTopHeight + this.space;
        //console.log(this.bottomPipePosition)
        //初始x轴坐标
        this.x = 300;
    }
    Pipe.prototype.update = function(){
        this.x -= 2;
        //超出屏幕的管子 从数组中删除
        if(this.x < -52){
            game.pipes = _.without(game.pipes,this);
            game.types = _.without(game.types,this);
        }
        //管子的坐标
        this.x1 = this.x;
        this.x2 = this.x + 52;
        this.y1 = this.randomTopHeight;
        this.y2 = this.randomTopHeight + this.space;
        game.ctx.fillText("x1:"+ this.x1,this.x1 - 50, this.y1);
        game.ctx.fillText("x2:"+ this.x2,this.x2 + 10, this.y1);
        game.ctx.fillText("y1:"+ this.y1,this.x1, this.y1 + 20);
        game.ctx.fillText("y2:"+ this.y2,this.x1, this.y1 + 100);
        

        //碰管判断
        if(game.bird.x2 > this.x1 && game.bird.x1 < this.x2 && game.bird.y2 > this.y2 ||
            game.bird.x2 > this.x1 && game.bird.x1 < this.x2 && game.bird.y1 < this.y1 ||
            game.bird.y2 > game.canvas.height - 112){
            //alert("碰到了")
            //碰到管子立即停止定时器
            clearInterval(game.timer);
            //重启定时器完成部分逻辑
            game.timer = setInterval(() => {
                //背景渲染
                game.background.render();
                //大地动起来 
                game.land.render();
                //管子渲染
                _.each(game.pipes,function (type) {
                    type.render();
                })
                //小鸟状态
                //头朝下
                game.bird.rot = 2.2;
                //向下运动
                game.bird.direction = "DOWN";
                //更新渲染小鸟
                game.bird.update();
                game.bird.render();
                //小鸟落地后清除定时器
                if(game.bird.y >= game.canvas.height - 118){
                    clearInterval(game.timer);
                    //恢复场景
                    game.scene = new SceneManager();
                    //game.ctx.clearRect(0,0,game.canvas.width,game.canvas.height);
                    game.scene.enter(0);
                    //场景渲染更新
                    game.timer=setInterval(function () {
                        game.scene.render();    
                    },20)
                }
            }, 20);
        }else{
             console.log(game.bird.x1)
            if(game.bird.x1 == this.x2){
                game.score ++;
            }
        }
    }
    Pipe.prototype.render = function(){
        //渲染图片
        game.ctx.drawImage(this.pipeDown,0,320-this.randomTopHeight,52,this.randomTopHeight,this.x,0,52,this.randomTopHeight);
        game.ctx.drawImage(this.pipeUp,0,0,52,this.randomBottomHeight,this.x,this.bottomPipePosition,52,this.randomBottomHeight);
    }
})()

六、Bird类

(function () {
    window.Bird = function () {
        //存到game的  维护类的数组中
        game.types.push(this);
        //小鸟的图片数组
        this.imageArr = [game.Robj["bird0_0"],game.Robj["bird0_1"],game.Robj["bird0_2"]];
        //小鸟默认第一张图片
        this.wing = 0;
        //小鸟默认位置
        this.x = 60;
        this.y = 100;
        //小鸟初始化向下运动  有限状态机
        this.direction = "DOWN";
        //小鸟的宽高
        this.width = 48;
        this.height = 48;
        //小鸟下落的 加速度
        this.idx = 0;
        //小鸟下落的时候 头朝下 利用变形的  rotate 设置默认角度
        this.rot = 0.4;
        
    }
    Bird.prototype.update = function () {
        /* this.wing ++ ;
        if(this.wing > 2){
            this.wing = 0;
        } */
        //效果同上
        if(game.frame % 4 == 0){
            this.wing = ++this.wing%3;
        }
        //判断上升还是下落
        if(this.direction == "DOWN"){
            //翅膀不挥舞
            this.wing = 0;
            this.rot = 0.4;
            this.idx += 0.2;
            this.y += this.idx;
        }else{
            //小鸟上升
            this.rot = -0.4;
            this.idx += -0.2;
            // 如果idy的值为负数,则代表小鸟是一个下降的状态
            if(this.idx > 0){
                this.y -= this.idx;
            }else{
                // idy小于0 的逻辑,两个作用,一个是实际上更改有限状态机的状态为DOWN,第二个作用是小鸟如果是下落不应该挥动翅膀
                this.direction = "DOWN";
            }
        }
        //超出大地 
        if(this.y > game.canvas.height-118) this.y = game.canvas.height - 118;
        //超出顶部
        if(this.y < 8) this.y = 8;
        //测试文字 
        game.ctx.font = "14px 微软雅黑";
        game.ctx.fillStyle="red";
        game.ctx.fillText("idx"+this.idx,20, 40);
        game.ctx.fillText("y"+this.y,20,60);


        //小鸟的坐标
        this.x1 = this.x - 10;
        this.x2 = this.x + 10;
        this.y1 = this.y - 14 ;
        this.y2 = this.y + 14; 
        game.ctx.fillText("x1:"+this.x1,this.x1 - 50,this.y1 + 15);
        game.ctx.fillText("x2:"+this.x2,this.x2 + 10,this.y1 + 15);
        game.ctx.fillText("y1:"+this.y1,this.x1 + 20,this.y1 - 15);
        game.ctx.fillText("y2:"+this.y2,this.x1 + 20,this.y1 + 40);
    }
    Bird.prototype.render = function () {

        //利用变形进行运动
        //备份
        game.ctx.save();
        //平移
        game.ctx.translate(this.x,this.y);
        //旋转
        game.ctx.rotate(this.rot);
        //旋转中心为 图片中心
        //game.ctx.drawImage(this.imageArr[this.wing],this.width,this.height);
        game.ctx.drawImage(this.imageArr[this.wing],-this.width/2,-this.height/2);
        game.ctx.restore();
    }
    Bird.prototype.fly = function () {
        this.direction = "UP";
        this.idx = 5;
       
    }
})()

七、场景管理器

将所有的场景内容都封装到一个场景管理器的类中,所有的更新渲染逻辑都在场景管理器中,而不是在game中

(function(){
    window.SceneManager = function(){
        this.bindEvent();
        //小鸟初始运动方向
        this.moveDirection = "DOWN";
        //帧编号
        this.frame = 0;
    }
    //进入场景的方法
    SceneManager.prototype.enter = function(num){
        //对应场景初始化逻辑
        game.sceneNum = num;
        switch(game.sceneNum){
            case 0 :
                //入场背景图
                this.w = game.canvas.width;
                this.h = game.canvas.height;
                //title 图片
                this.titleY = -50;
                //小鸟图
                this.birdY = 150;
                //渲染按钮
                break;
            case 1 :
                //小鸟图
                this.birdY = 40;
                //引导图初始透明度
                this.alpha = 1;
                //是否变透明了
                this.isAlpha = true;
                break;
            case 2 :
                 //维护管子类
                game.pipes = [];
                //初始化 管子 和 类
                game.types = [];
                //创建背景大地
                game.background = new Background();
                game.land = new Land();
                //new 小鸟
                game.bird = new Bird();
               //分数初始化
               game.score = 0;
                break;


        }
    }
    //渲染和更新
    SceneManager.prototype.render = function(){
        this.frame ++;
        //对应场景渲染逻辑
        switch(game.sceneNum){
            case 0 :
                //渲染入场背景图
                game.ctx.drawImage(game.Robj["bg_night"],0,0);
                //渲染title图
                this.titleY += 2;
                if(this.titleY > 100) this.titleY = 100;
                game.ctx.drawImage(game.Robj["title"],game.canvas.width/2 - 89,this.titleY);
                //小鸟图  让小鸟来回不同运动 
                if(this.moveDirection == "DOWN"){
                    this.birdY += 2;
                    if(this.birdY > 250)  this.moveDirection = "UP";
                }else{
                    this.birdY -= 2;
                    if(this.birdY < 150)  this.moveDirection = "DOWN";
                }
                //渲染小鸟
                game.ctx.drawImage(game.Robj["bird0_0"],game.canvas.width/2 - 24,this.birdY);
                //渲染btn
                game.ctx.drawImage(game.Robj["button"],game.canvas.width/2 - 58,300)
                break;
            case 1 :
                //渲染背景图
                game.ctx.drawImage(game.Robj["bg_day"],0,0);
                //小鸟图  让小鸟来回不同运动 
                if(this.moveDirection == "DOWN"){
                    this.birdY += 2;
                    if(this.birdY > 120)  this.moveDirection = "UP";
                }else{
                    this.birdY -= 2;
                    if(this.birdY < 40)  this.moveDirection = "DOWN";
                }
                //渲染小鸟
                game.ctx.drawImage(game.Robj["bird0_0"],game.canvas.width/2 - 24,this.birdY);
                //透明度 变形
                game.ctx.save();
                //不停的闪
                if(this.isAlpha){
                    this.alpha -= 0.1;
                    if(this.alpha <= 0) this.isAlpha = false;
                }else{
                    this.alpha += 0.1;
                    if(this.alpha >=1) this.isAlpha = true;
                }
                game.ctx.globalAlpha = this.alpha;
                game.ctx.drawImage(game.Robj["tutorial"],game.canvas.width/2 - 57, 250)
                game.ctx.restore();
                //引导图
                break;
            case 2 :
                //渲染背景大地
                _.each(game.types,function (type) {
                    type.update();
                    type.render();
                })
                //new 管子
                //game.pipes = new Pipe();
                if(this.frame % 100 == 0){
                    game.pipes.push(new Pipe());
                }
                game.addScore()
                break;
        }
    }
    SceneManager.prototype.bindEvent = function(){
        var self = this;
        game.canvas.onclick = function(event){
            //鼠标点击位置
            var x = event.offsetX;
            var y = event.offsetY;
            switch (game.sceneNum) {
                case 0:
                    if(x > game.canvas.width/2 - 58 && x <  game.canvas.width/2 + 58 && y > 300 && y < 364){
                        self.enter(1);
                    }
                    break;
                case 1:
                    if(x > game.canvas.width/2 - 57 && x <  game.canvas.width/2 + 57 && y > 250 && y < 250+98){
                        self.enter(2);
                    }
                break;
                case 2:
                    game.bird.fly();
                    break;
            }
           

        }
    }
})()

index.html

<body>
    <canvas width="288" height="512"></canvas>
    <script src="js/underscore.js"></script>
    <script src="js/Game.js"></script>
    <script src="js/Background.js"></script>
    <script src="js/Land.js"></script>
    <script src="js/Pipe.js"></script>
    <script src="js/Bird.js"></script>
    <script src="js/SceneManager.js"></script>
    <script>
        var game = new Game()
    </script>
</body>

 

 类似资料: