博主学习前端一年多一点,还是个新手,不过勇于尝试,才能不断进步,如果代码质量不好,欢迎提意见,下面开始讲解,首先贴张游戏界面图:
游戏使用canvas画图制作,分析游戏确定有这几个元素:
- 天空背景不动
- 小鸟上下移动,左右不动
- 地板和水管向左移动(造成小鸟向前移动的错觉)
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 {
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 =