FlappyBird的风靡,是不是让同学们也有了一个自己制作这个游戏的冲动呢?下来我就把整个游戏的制作流程做一个介绍。如有不足之处还请各位大佬海涵。
首先准备好素材,以下一张图片是我从网上找到的素材图。实际制作中可以根据自己的想法替换成别的更搞笑的素材。

简要流程:
简要代码:
1 window.onload = function () { //页面加载完成 2 imageLoaded(); 3 } 4 5 function imageLoaded () { 6 var image = new Image(); 7 image.src = 'flappyBird.png'; 8 image.onload = function () { //图片加载完成 9 var canvas = document.getElementById('canvas'); 10 var cxt = canvas.getContext('2d'); 11 data.image = image; 12 drawImage(cxt); //canvas绘图 13 [通过某事件进入GetReady] 14 } 15 } 16 17 function getReady () { 18 [单击事件进入gamePlaying] 19 } 20 21 function gamePlaying () { 22 [失败则进入fail] 23 } 24 25 function fail () { 26 [通过某事件进入GetReady] 27 } 28 29 function drawImage (cxt) { 30 [绘制代码] 31 }
我们再定义一个全局变量data,游戏中所有的数据都从这里获取或修改,当然在做好游戏后玩的过程中可以通过F12修改其中数值达到外挂的效果~
1 var data = { 2 image: null, //将图片元素存入,在调用canvas绘制函数时用 3 score: 0, //当前得分 4 bestScore: 0, //历史得分,更早获得过的最高分,通过cookie实现 5 system: { 6 dataRefreshRate: 20, //数据刷新率,游戏是每一个周期改变数据达到运行的目的,用一个统一的值防止混乱 7 screenRefreshRate: 20, //屏幕刷新率,做第一版的时候没考虑这个问题,结果导致配置低的机子以每20毫秒刷新一次的话(即50hz)会特别卡,所以这版将这个分离出来 8 start: false, //游戏是否开始 9 fail: false, //游戏失败与否 10 }, 11 element: { //游戏中的所有元素,其中的show为是否显示,draw为元素对应的绘制函数 12 bird: { //鸟,其实整个游戏可以看作小鸟水平方向静止不动,障碍物向左运动 13 show: true, 14 color: 0, //0黄色,1蓝色,2红色 15 attitude: 0, //飞行姿态,0~2 16 speedY: 0, //下落速度 17 left: 120, //距左边界距离 18 top: 315, //距上边界距离 19 gravity: 0, //重力数值 20 animate: false, //是否运动 21 draw: drawBird 22 }, 23 background: { //背景 24 show: true, 25 type: 0, //0白天,1黑夜 26 draw: drawBackground 27 }, 28 bottomStripe: { //底部滚动条 29 show: true, 30 move: true, //是否移动 31 deviation: 0, //偏移量 32 draw: drawBottomStripe 33 }, 34 obstacle: { //柱子,即障碍物 35 show: false, 36 previousAdopt: 56, //距离上个障碍物的距离 37 adopt: 140, //可通过上下柱之间高度 38 width: 72, //宽度 39 body: [], //用于存储障碍物的队列,重要 40 draw: drawObstacle 41 }, 42 title: { //标题 43 show: true, 44 type: 0, //0 flappyBird, 1 Get Ready, 2 Game Over 45 top: 0, //距顶端距离 46 draw: drawTitle 47 }, 48 tip: { //点击提示 49 show: false, 50 draw: drawTip 51 }, 52 startButton: { //开始按钮 53 show: false, 54 draw: drawStartButton 55 }, 56 score: { //分数 57 show: false, 58 value: 0, //分数值 59 draw: drawScore 60 }, 61 rankings: { //分数榜单 62 show: false, 63 top: 600, //距顶端距离 64 draw: drawRankings 65 }, 66 mask: { //黑幕,渐变效果使用 67 show: false, 68 alpha: 0, //透明度 69 draw: drawMask 70 } 71 }, 72 TIME: {} //setInterval与setTimeout的存储位置 73 } 74
例如data.element.startButton.draw值为drawStartButton,对应函数为:
function drawStartButton (cxt) { cxt.drawImage(data.image, 708, 236, 104, 58, 128, 400, 144, 81); }
现在开始按游戏流程制作:当页面加载完成后,进行一系列数据初始化,在window.onload中添加执行函数resetData();
function resetData () { data.score = 0; //分数重置为0 data.element.bird.color = parseInt(Math.random() * 1000) % 3; //鸟的颜色随机3选1 data.element.background.type = parseInt(Math.random() * 1000) % 2; //背景随机2选1 data.element.bird.top = 315; //初始化鸟的高度 data.element.bird.speedY = 0; //初始化鸟的垂直速度 data.element.bird.gravity = 0; //初始化重力 data.element.obstacle.previousAdopt = 56; //距离下个阻挡物的距离 data.element.obstacle.body = []; //清空存储阻挡物的队列 }
然后编辑image.onload中的drawImage(cxt):
for循环将每个show值为true的组件依次绘制,一定要注意绘制顺序,最底层的要先绘制,最顶层的最后绘制,之所以不用for i in arr的原因也是怕其影响顺序,注意其周期是data.system.screenRefreshRate,也是唯一使用此数据的地方。若手机运行吃力,不妨将此周期延长。
function drawImage (cxt) { var drawOrder = ['background', 'obstacle', 'bottomStripe', 'title', 'tip', 'startButton', 'score', 'rankings', 'bird', 'mask']; data.TIME.drawImage = setInterval(function () { for(var i = 0, len = drawOrder.length; i < len; i++){ if (data.element[drawOrder[i]].show === true) { data.element[drawOrder[i]].draw(cxt); } } }, data.system.screenRefreshRate); }
我们再写一个简单函数,传入名称与布尔值,改变数据的show值,以方便其显示或隐藏
function showElement (name, show) { data.element[name].show = show ? true : false; }
再回到image.onload中,图片载入完成后可将标题、开始按钮、底层运动条纹显示出来,还有小鸟,并使小鸟做出飞翔动作。
在图片素材中可以看到小鸟动作总共3帧,依次绘制实现动画效果,函数传入参数可以控制小鸟停止动作与否。
function birdAnimate (animate) { if (animate === true) { data.TIME.birdAnimate = setInterval(function () { data.element.bird.attitude = (data.element.bird.attitude + 1) % 3; }, data.system.dataRefreshRate * 9); } else { clearInterval(data.TIME.birdAnimate); } }
绘制小鸟的参数,将素材中的关键点提前写死在函数中,而且随着垂直速度的上升或下降进行旋转,具体实现参考canvas的旋转部分,这里不赘述。
function drawBird (cxt) { var birdPosition = [ [ [6, 982], [62, 982], [118, 982] ], [ [174, 982], [230, 658], [230, 710] ], [ [230, 762], [230, 814], [230, 866] ] ]; cxt.save(); cxt.translate(data.element.bird.left, data.element.bird.top); cxt.rotate(Math.atan(data.element.bird.speedY / 10)); cxt.drawImage(data.image, birdPosition[data.element.bird.color][data.element.bird.attitude][0], birdPosition[data.element.bird.color][data.element.bird.attitude][1], 34, 24, -24, -17, 48, 34); cxt.restore(); }
再给canvas添加鼠标单击事件,当鼠标单击它且单击的位置位于按钮内时,进入getReady状态,而且当后面游戏失败后,重新开始时也是进入getReady状态。
function getReady () { data.element.title.type = 1; //标题变为Get Ready resetData(); //重置数据 showElement('score', true); //显示分数 showElement('tip', true); //显示点击提示 showElement('startButton', false); //隐藏开始按钮 dataUpdata(true); if (data.system.start === false) { //当游戏未开始时执行,相当于一局游戏中只执行一次 data.element.bird.gravity = 0.4; //设置重力 showElement('title', false); //隐藏标题 showElement('tip', false); //隐藏点击提示 showElement('obstacle', true); //显示障碍物 createObstacle(); //构建障碍物 } data.system.start = true; data.element.bird.speedY = -7; //每次单击时,将小鸟垂直速度强制为-7,以达到单击时小鸟跳动效果 }
其中的dataUpdate函数:
function dataUpdata (update) { //传递update也起到开启关闭作用 if (update === true) { //每次数据变化:鸟的速度为速度加上重力加速度,而距离顶端高度为高度再加上速度 data.TIME.dataUpdate = setInterval(function () { data.element.bird.speedY = data.element.bird.speedY + data.element.bird.gravity; data.element.bird.top = data.element.bird.top + data.element.bird.speedY; }, data.system.dataRefreshRate); } else { clearInterval(data.TIME.dataUpdate); } }
这是一个比较关键的部分,构建障碍物:
function createObstacle () { data.TIME.obstacle = setInterval(function () { data.element.obstacle.previousAdopt = data.element.obstacle.previousAdopt + 1; //每一个周期障碍物的距离加1,当达到一定值后归零,创建新障碍物 for (var i = 0, len = data.element.obstacle.body.length; i < len; i++) { //所有已存在的障碍物向左移动,数值可微调以达到自然效果 data.element.obstacle.body[i][0] -= 0.54; } if (data.element.obstacle.previousAdopt > 448) { data.element.obstacle.previousAdopt = 0; var obstacleTop = parseInt(Math.random() * 260) + 100, //随机生成缺口高度 obstacleBottom = obstacleTop + data.element.obstacle.adopt; data.element.obstacle.body.push([400, obstacleTop, obstacleBottom]); // data.element.obstacle.body中推入一个数组:[其距离左端的距离, 上端距, 下端距] } for (var i = 0; i < data.element.obstacle.body.length; i++) { if (data.element.obstacle.body[0][0] < -72) { data.element.obstacle.body.shift(); //当最左边的障碍物超出视距后,清除。 } } }, data.dataRefreshRate); }
当data.element.obstacle.body长度大于等于1时则进行绘制,具体方法与小鸟绘制方法相似。
每个数据周期须进行判定小鸟是否碰撞,我们将它放在data.TIME.dataUpdate的setInterval里面:
if (!collisionJudge()) { fail(); }
其中小鸟可近似看成矩形,满足其存活的条件为:1.不落到地面上,2.不碰到障碍物上。
不落到地面上即data.element.bird.top小于某固定值即可,而不碰到障碍物上即障碍物运动到小鸟的位置时,要保证小鸟的高度在障碍物中间缝隙之间,每次只需要判断当前位置的障碍物是否与小鸟相撞,返回布尔值即可。
function fail其实就比较简单了,其中具体内容为清除之前的一系列Interval,小鸟动作静止,显示分数、排行榜等,及再次回到getReady的按钮,此时完成一个闭环,游戏初步完成。
以下是自己完成度较高的代码,不足之处还望各位大佬指出,共同学习进步。
浙公网安备 33010602011771号