游戏查看
源码和素材下载
博主学习前端一年多一点,还是个新手,不过勇于尝试,才能不断进步,如果代码质量不好,欢迎提意见,下面开始讲解,首先贴张游戏界面图:
游戏使用canvas画图制作,分析游戏确定有这几个元素:
- 天空背景不动
- 小鸟上下移动,左右不动
- 地板和水管向左移动(造成小鸟向前移动的错觉)
canvas画图是覆盖画图,所以画图顺序很重要,它的api我就不多说了,只有用到以下内容:
<canvas id="canvas">您的浏览器不支持canvascanvas>
/* 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 {
width: 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种,如图:
只要符合上面一种情况就不重合,其余情况就是重合,所以:
//检测两个矩形是否重合,可以反着看,先找出矩形不重合的情况,
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代码
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flappy Birdtitle>
<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 {
width: 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;
};