Flappy Bird(Canvas 实现)

@[TOC](Flappy Bird(Canvas 实现))

资源源码下载https://download.csdn.net/download/qq_37644845/11267687

Flappy Bird

一、Canvas基础

  1. 创建 canvas 标签注意事项
[html]
<canvas width="360" height="520" id="myCanvas"></canvas>
[javascript]
// 获取canvas对象
/**@type { HTMLCanvasElement }*/
var canvas=document.getElementById('myCanvas');
// 获取画布对象
var ctx=canvas.getContext('2d');
[注]
canvas标签:宽高最好写在标签内而不是css样式表,否则画布可能会变形
/**@type { HTMLCanvasElement }*/:为了标记变量canvas的类型,方便下文调用其属和方法;
  1. canvas 绘图操作
// 1、画方块(坐标(100,100);宽高(100,100))
ctx.fillRect(100,100,100,100);
// 2、清除画布
ctx.clearRect(0,0,canvas.weight,canvas.height);
// 3、加载图片
var image=new Image();
image.src='images/xx.png';
image.onload=function(){
   //ctx.drawImage();的参数的个数可变化为3、5、9
   ctx.drawImage(image,100,100);
}
// 4、其余 略

二、组成实现

  1. 实现背景移动
// 在实现功能之前先搭一个简易的游戏框架(非常简易)
[js]
// 使用了SimpleJavaScriptInheritance.js(网上可下载、方便进行类的继承)
[Game.js]
(function(){
	var Game=window.Game=Class.extend({
		init:function(){
			// 构造函数
		},
		loadResources:function(){
			// 相当于原生js使用prototype.loadResources
			// 加载资源、所有图片加载完成后才能进行渲染
			
		},
		start:function(){
			// 游戏的主循环位置,游戏画面每秒 50 帧左右
			this.timer=setInterval(function(){
			
			},20);
		}
		, bindEvent: function () {
           // 添加方法、主要是交互方法
        }
	});
})();
// 使用中介者模式来创建游戏实例子
var game=new Game();
[Background.js]
// 突然就不想总结的这么详细了,那么就把我认为重要的写下
// 略
  1. 实现管子添加
// 略
  1. 小鸟飞行、以及画布事件检测
// 略

终. 效果图
在这里插入图片描述在这里插入图片描述

三、场景管理器的引用(升级版)

  1. 游戏实现多场景切换:
    将游戏中元素加载、刷新放入场景管理器中并设置一个SceneNumber来确定当前场景
    结构如下
(function () {
    var Scene = window.Scene = Class.extend({
        init: function () {
            // 场景0 准备
            // 场景1 游戏教程
            // 场景2 游戏开始
            // 场景3 小鸟死亡
            // 场景4 分数界面
            this.sceneNumber = 0;
        },
        update: function () {
         	// 游戏主循环中刷新的函数
         	switch (self.sceneNumber) {
					case 0:break;
					case 1:break;
					case 2:break;
					case 3:break;
					case 4:break;
			   }  
         },
        render: function () {
        	 // 游戏主循环中加载的函数
        	 switch (self.sceneNumber) {
					case 0:break;
					case 1:break;
					case 2:break;
					case 3:break;
					case 4:break;
			   }  
         },
        enter: function (sceneNumber) { 
			 // 游戏切换场景的函数
			 switch (self.sceneNumber) {
					case 0:break;
					case 1:break;
					case 2:break;
					case 3:break;
					case 4:break;
			   }  
		},
        bindEvent: function (event) {
        	  var mX = event.clientX;
              var mY = event.clientY;
               switch (self.sceneNumber) {
					case 0:break;
					case 1:break;
					case 2:break;
					case 3:break;
					case 4:break;
			   }  
        }
    });
})();

终.

  1. 场景1 开始
    在这里插入图片描述 2. 场景2
    在这里插入图片描述3.场景3 本人降低了游戏的难度、便于测试、开口极大
    在这里插入图片描述4、场景4 死亡爆炸场景
    在这里插入图片描述5. 场景5 分数场景
    在这里插入图片描述

四、部分源码(升级版)

  1. SceneManage.js 场景管理器
// 场景管理器
(function () {
    var Scene = window.Scene = Class.extend({
        init: function () {
            // 场景0 准备
            // 场景1 游戏教程
            // 场景2 游戏开始
            // 场景3 小鸟死亡
            this.sceneNumber = 0;
            // 场景0 logo y值
            //this.s_0_logoY = 0;
            //this.s_0_btnY = game.canvas.height;
            //this.s_0_globalAlpha = 1;
            //this.s_0_globalAlpha_isRise = false;
             场景3 中 的小帧号\ 爆炸编号
            //this.s_3_fno = 0;
            //this.s_3_boom = 0;
            //this.s_3_bird_isShow = true;
             场景4、升起成绩版 \小帧号
            //this.s_4_fno = 0;
            //this.s_4_textY = 0;
            //this.s_4_panelY = game.canvas.height;
             当前成绩的奖牌
            //this.s_4_medals = null;
            // 进入场景0;
            this.enter(0);
            this.bindEvent();
        },
        update: function () {
            switch (this.sceneNumber) {
                case 0:
                    game.bg.update();
                    game.land.update();
                    this.s_0_logoY += 5;
                    if (this.s_0_logoY > 100) {
                        this.s_0_logoY = 100;
                    }
                    this.s_0_btnY -= 12;
                    if (this.s_0_btnY < 250) {
                        this.s_0_btnY = 250;
                    }
                    break;
                case 1:
                    game.bg.update();
                    game.land.update();
                    break;
                case 2:
                    game.bg.update();
                    game.land.update();
                    break;
                case 3:
                    
                    break;
                case 4:
                    this.s_4_textY += (20 - this.s_4_fno * 10);
                    this.s_4_panelY -= ((parseInt(game.canvas.height - 165) / 5) - this.s_4_fno * 10);
                    if (this.s_4_textY > 100) {
                        this.s_4_textY = 100;
                    }
                    if (this.s_4_panelY < 165) {
                        this.s_4_panelY = 165;
                    }
                    break;
                default:
            }
        },
        render: function () {
            // 加载背景
            game.bg.render();
            // 加载大地
            game.land.render();
            switch (this.sceneNumber) {
                case 0:
                    game.ctx.drawImage(game.R["logo"], game.canvas.width / 2 - game.R["logo"].width / 2, this.s_0_logoY);
                    game.ctx.drawImage(game.R["button_play"], game.canvas.width / 2 - game.R["button_play"].width / 2, this.s_0_btnY);
                    // 加载鸟、保持鸟在屏幕中间飞
                    game.bird.update();
                    game.bird.y = 165;
                    game.bird.x = game.canvas.width / 2 - game.bird.images[0].width / 2;
                    game.bird.d = 0;
                    game.bird.fno = 0;
                    game.bird.render();
                    break;
                case 1:
                    // 加载鸟、保持鸟在屏幕中间飞
                    game.bird.update();
                    game.bird.y = 165;
                    game.bird.x = game.canvas.width / 2 - game.bird.images[0].width / 2;
                    game.bird.d = 0;
                    game.bird.fno = 0;
                    game.bird.render();
                    game.ctx.save();
                    this.s_0_globalAlpha +=(this.s_0_globalAlpha_isRise ? 0.1 : -0.1);
                    if (this.s_0_globalAlpha > 0.9 || this.s_0_globalAlpha<0.1) {
                        this.s_0_globalAlpha_isRise = !this.s_0_globalAlpha_isRise;
                    }
                    game.ctx.globalAlpha = this.s_0_globalAlpha;
                    //this.s_0_globalAlpha 
                    game.ctx.drawImage(game.R['tutorial'], game.canvas.width / 2 - game.R['tutorial'].width / 2, game.bird.y + 100);
                    game.ctx.restore();
                    game.ctx.drawImage(game.R['text_ready'], game.canvas.width / 2 - game.R['text_ready'].width / 2,100);
                    break;
                case 2:
                    if (game.f % 80 === 0) {
                        new Pipe();
                    }
                    for (var i = 0; i < game.actors.length; i++) {
                        // 加载所有管子类
                        if (game.actors[i] instanceof Pipe) {
                            game.actors[i].update();
                            game.actors[i].render();
                        }
                    }
                    game.bird.update();
                    game.bird.render();
                    // 计算得分
                    for (var i = 0; i < game.score.toString().length; i++) {
                        game.ctx.drawImage(game.R['shu' + game.score.toString().charAt(i)], game.canvas.width / 2 - game.score.toString().length *14 + 28 * i, 82);
                    }
                    break;
                case 3:
                    this.s_3_bird_isShow ? game.bird.render() : '';
                    for (var i = 0; i < game.actors.length; i++) {
                        // 加载所有管子类
                        if (game.actors[i] instanceof Pipe) {
                            game.actors[i].render();
                        }
                    }
                    // 计算得分
                    for (var i = 0; i < game.score.toString().length; i++) {
                        game.ctx.drawImage(game.R['shu' + game.score.toString().charAt(i)], game.canvas.width / 2 - game.score.toString().length * 14 + 28 * i, 82);
                    }
                    // 如果鸟不在地上,让其落在地上
                    if (game.bird.y < 368) {
                        game.bird.y += 0.25 * this.s_3_fno;
                        this.s_3_fno++;
                    } else {
                        this.s_3_bird_isShow = false;
                        game.ctx.drawImage(game.R['bird_boom'], (this.s_3_boom % 4) * 128, parseInt(this.s_3_boom / 4) * 128, 128, 128, game.bird.x - 38, game.bird.y - 38, 128, 128);
                        this.s_3_boom++;
                        if (this.s_3_boom > 16) {
                            // 进入场景4
                            game.scene.enter(4);
                        }
                    }
                    break;
                case 4:
                    game.ctx.drawImage(game.R["text_game_over"], game.canvas.width / 2 - game.R["text_game_over"].width / 2, this.s_4_textY);
                    game.ctx.drawImage(game.R["score_panel"], game.canvas.width / 2 - game.R["score_panel"].width / 2, this.s_4_panelY);

                    game.ctx.drawImage(this.s_4_medals, game.canvas.width / 2 - game.R["score_panel"].width / 2 + 30, this.s_4_panelY + 44);
                    game.ctx.drawImage(game.R["button_menu"], game.canvas.width / 2 - game.R["button_menu"].width / 2, this.s_4_panelY + 144);
                    game.ctx.font = '20px 微软雅黑';
                    game.ctx.fillText(game.score, game.canvas.width / 2 - game.R["score_panel"].width / 2 + 208 - game.score.toString().length*11, this.s_4_panelY + 53);
                    game.ctx.fillText(game.bestScore, game.canvas.width / 2 - game.R["score_panel"].width / 2 + 208 - game.bestScore.toString().length * 11, this.s_4_panelY + 95);
                    
                    break;
                default:
            }
        },
        enter: function (sceneNumber) {
            this.sceneNumber = sceneNumber;
            switch (this.sceneNumber) {
                case 0:
                    game.score = 0;
                     // 场景0 logo y值
                    this.s_0_logoY = 0;
                    this.s_0_btnY = game.canvas.height;
                    this.s_0_globalAlpha = 1;
                    this.s_0_globalAlpha_isRise = false;
                    // 固定位置
                    game.bird.y = 165;
                    game.bird.x = game.canvas.width / 2 - game.bird.images[0].width / 2;
                    // 清除水管
                    for (var i = (game.actors.length-1); i >=0; i--) {
                        if (game.actors[i] instanceof Pipe) {
                            game.actors.splice(i, 1);
                        }
                    }
                    break;
                case 1:
                    break;
                case 2: break;
                case 3:
                     场景3 中 的小帧号\ 爆炸编号
                    this.s_3_fno = 0;
                    this.s_3_boom = 0;
                    this.s_3_bird_isShow = true;
                    break;
                case 4:
                     // 场景4、升起成绩版 \小帧号
                    this.s_4_fno = 0;
                    this.s_4_textY = 0;
                    this.s_4_panelY = game.canvas.height;
                    this.s_4_medals = null;
                    // 进入第4场景、存储分数
                    game.historyScore.push(game.score);
                    if (game.score > game.bestScore) {
                        game.bestScore = game.score;
                    }
                    // 获取奖牌
                    if (game.score < 5) {
                        this.s_4_medals = game.R['medals_0'];
                    } else if (game.score < 10 && game.score>=5) {
                        this.s_4_medals = game.R['medals_1'];
                    } else if (game.score < 20 && game.score >= 10) {
                        this.s_4_medals = game.R['medals_2'];
                    } else if (game.score >= 20) {
                        this.s_4_medals = game.R['medals_3'];
                    }
                    break;
                default:
            }
        },
        bindEvent: function () {
            var self = this;
            game.canvas.onclick = function (event) {
                var mX = event.clientX;
                var mY = event.clientY;
                switch (self.sceneNumber) {
                    case 0:
                        if (mY > (self.s_0_btnY + 4) && mY < (self.s_0_btnY + 60) && mX > (game.canvas.width / 2 - 52) && mX < (game.canvas.width / 2 + 52)) {
                            self.enter(1);
                        }
                        break;
                    case 1:
                        self.enter(2);
                        break;
                    case 2:
                        game.bird.fly();
                        break;
                    case 3: break;
                    case 4:
                        // game.canvas.width / 2 - game.R["button_menu"].width / 2, this.s_4_panelY + 144
                        if (mY > (self.s_4_panelY + 144) && mY < (self.s_4_panelY + 172) && mX > (game.canvas.width / 2 - game.R["button_menu"].width / 2) && mX < (game.canvas.width / 2 - game.R["button_menu"].width / 2 + 82)) {
                            // 换背景、换小鸟
                            for (var i = (game.actors.length-1); i >=0; i--) {
                                if ((game.actors[i] instanceof Background) || (game.actors[i] instanceof Bird)) {
                                    game.actors.splice(i, 1);
                                }
                            }
                            game.bg = new Background();
                            game.bird = new Bird();
                            self.enter(0);
                        }
                        break;
                    default:
                }
            };
        }
    });
})();
  1. Actors.js 管理演员(对象)的类
(function () {
    var Actor = window.Actor = Class.extend({
        init: function () {
            game.actors.push(this);
        }
        , update: function () {

        }
        , render: function () {
            throw new Error('必须重写render方法');
        }
    });
})();
  1. Game.js 游戏类、也是中介者模式中的中介者
(function () {
    var Game = window.Game = Class.extend({
        // 构造函数
        init: function (id) {
            /**@type { HTMLCanvasElement }*/
            this.canvas = document.getElementById(id);
            // 得到上下文
            this.ctx = this.canvas.getContext('2d');
            // R文件的路径
            this.RTextUrl = 'R.txt';
            // 自己的图片资源对象,v是图片路径
            this.RObj = null;
            this.R = {};
            // 游戏历史数据
            this.historyScore = [];
            this.bestScore = 0;
            // 所有演员类
            this.actors = [];
            // 帧编号
            this.f = 0;
            // 得分
            this.score = 0;
            var self = this;
            // 加载所有资源,这个函数的终点就是循环的开始
            this.loadResources(function () {
                this.start();
                // self.bindEvent();
            });
        }
        // 加载所有资源
        ,loadResources: function (callback) {
            var self = this;
            // 已经加载好的图片编号
            var count = 0;
            // 提示正在加载图片
            self.ctx.font = "30px 微软雅黑";
            self.ctx.textAlign = 'center';
            self.ctx.fillText("正在加载图片...", self.canvas.width / 2, self.canvas.height * (1 - 0.618));
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
                    self.RObj = JSON.parse(xhr.responseText);
                    // 图片总数
                    var imageAmount = 0;
                    for (var i in self.RObj) {
                        imageAmount++;
                    }
                    // 遍历对象
                    for (var k in self.RObj) {
                        // 创建image对象
                        self.R[k] = new Image();
                        // 发出SRC请求
                        self.R[k].src = self.RObj[k];
                        self.R[k].onload = function () {
                            // 计数器加 1
                            count++;
                            // 清屏
                            self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
                            self.ctx.fillText("正在加载图片" + count + '/' + imageAmount, self.canvas.width / 2, self.canvas.height * (1 - 0.618));
                            if (count == imageAmount) {
                                // 开启游戏主循环
                                callback.call(self);
                            }
                        };
                    }
                }
            };
            xhr.open('get', this.RTextUrl, true);
            xhr.send(null);
        }
        , start: function () {
            var self = this;
            // 背景
            this.bg = new Background();
            // 大地
            this.land = new Land();
            // 加载鸟
            this.bird = new Bird();
            this.scene = new Scene();
            this.timer = setInterval(function () {
                self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
                //for (var i = 0; i < self.actors.length; i++) {
                //    self.actors[i].update();
                //    self.actors[i].render();
                //}
                //if (self.f % 80 === 0) {
                //    new Pipe();
                //}
                self.scene.update();
                self.scene.render();
                self.f++;
                self.ctx.font = '14px 宋体';
                self.ctx.textAlign = 'left';
                self.ctx.fillText('FNO ' + self.f, 20, 20); 
            }, 20);
        }
        //, bindEvent: function () {
        //    var self = this;
        //    this.canvas.onclick = function () {
        //        self.bird.fly();
        //    };
        //}
    });
})();

四、添加互动发音乐(待优化)

音乐素材均为互联网下载

[Music.js]
(function () {
    var Music = window.Music = Class.extend({
        init: function (src) {
            this.dom = document.createElement('audio');
            this.dom.src = src;
            // this.dom.autoplay = "autoplay";
            this.dom.style.display = 'none';
            this.dom.controls = "controls";
            this.dom.loop = "false";
            this.dom.hidden = "true";
            this.IsPlay = false;
            document.body.appendChild(this.dom);
        },
        play: function () {
            var self = this;
            this.IsPlay = true;
            this.dom.play();
            setTimeout(function () {
                self.IsPlay = false;
            }, self.dom.duration * 1000);
        },
        stop: function () {
            this.IsPlay = false;
            this.dom.pause();
            this.dom.load();
        }
    });
})();

// 配合动画使用,比如小鸟扇动翅膀
// 加载资源的时候创建
var a =new Music(src);

// 动画方法
game.bird.fly();
// 声音
a.play();
setTimeout(function () {
	// 定时关闭 ,否则自动单曲循环播放
	a.stop();
}, a.dom.duration * 1000);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值