如何用qt制作flappy bird电脑版

  START:最近闲来无事,看了看一下《C#开发Flappy Bird游戏》的教程,自己也试着做了一下,实现了一个超级简单版(十分简陋)的Flappy Bird,使用的语言是C#,技术采用了快速简单的WindowsForm,图像上主要是采用了GDI+,游戏对象的创建控制上使用了单例模式,现在我就来简单地总结一下。
一、关于Flappy Bird
  《Flappy Bird》是由来自越南的独立游戏开发者Dong Nguyen所开发的作品,游戏中玩家必须控制一只小鸟,跨越由各种不同长度水管所组成的障碍,而这只鸟其实是根本不会飞的&&所以玩家每点击一下小鸟就会飞高一点,不点击就会下降,玩家必须控制节奏,拿捏点击屏幕的时间点,让小鸟能在落下的瞬间跳起来,恰好能够通过狭窄的水管缝隙,只要稍一分神,马上就会失败阵亡。简单但不粗糙的8比特像素画面、超级马里奥游戏中的水管、眼神有点呆滞的小鸟和几朵白云,白天夜晚两种模式便构成了游戏的一切。玩家需要不断控制点击屏幕的频率来调节小鸟的飞行高度和降落速度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟不小心擦碰到了管子的话,游戏便宣告结束。
二、游戏设计
2.1 总结游戏印象
  玩过的Flappy Bird的童鞋们应该都对这款游戏有印象,现在我们来看看这款游戏的特点:
  (1)这款游戏的画面很简单:一张背景图,始终就没有变过;
  (2)这款游戏的对象只有俩:一个小鸟(有三种挥动翅膀的状态)以及一对管道(有管道向上和向下两个方向);
    小鸟:①②③
    管道:
2.2 总结设计思路
  (1)万物皆对象
  在整个游戏中,我们看到的所有内容,我们都可以理解为游戏对象;(在Unity中,GameObject即游戏对象)每一个游戏对象,都由一个单独的类来创建;在游戏中,总共只有两个游戏对象:小鸟和管道,那么我们就可以创建两个类:Bird和Pipe。但是,我们发现小鸟和管道都有一些共同的属性和方法,例如X,Y轴坐标,长度和宽度,以及绘制(Draw())和移动(Move())的方法,这时我们可以设计一个抽象类,将共有的东西封装起来,减少开发时的冗余代码,提高程序的可扩展性,符合面向对象设计的思路:
  (2)计划生育好
  在整个游戏中,我们的小鸟对象只有一个,也就是说在内存中只需要存一份即可。这时,我们想到了伟大的计划生育政策,于是我们想到了使用单例模式。借助单例模式,可以保证只生成一个小鸟的实例,即为程序提供一个全局访问点,避免重复创建浪费不必要的内存。
  (3)对象的运动
  在整个游戏中,小鸟会受重力默认向下坠落,而用户可以根据点击或按键盘Space键使小鸟向上飞,从图像呈现上其本质就是更改游戏对象在Y轴的位置,使其从下往上移动;而管道则会从屏幕右侧出现,从屏幕左侧消失,又从屏幕右侧出现,再从屏幕左侧消失,一直循环往复。可以看到,从图像呈现上期本质就是更改管道对象在X轴的位置,使其从右往左移动。
  (4)设计流程图
  在整个开发设计过程中,我们可以根据优先级设计开发流程,根据流程一步一步地实现整个游戏。
三、关键代码
3.1 设计抽象父类封装共有属性
/// &summary&
/// 游戏对象基类
/// &/summary&
public abstract class GameObject
#region 01.构造函数及属性
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public GameObject(int x, int y)
this.Width = this.Height = 0;
public GameObject(int x, int y, int width, int height)
this.Width =
this.Height =
#endregion
#region 02.抽象方法
/// &summary&
/// 抽象方法1:绘制自身
/// &/summary&
public abstract void Draw(Graphics g);
/// &summary&
/// 抽象方法2:移动自身
/// &/summary&
public abstract void Move();
#endregion
#region 03.实例方法
public Rectangle GetRectangeleArea()
return new Rectangle(this.X, this.Y, this.Width, this.Height);
#endregion
  一切皆对象,这里封装了游戏对象小鸟和管道共有的属性,以及两个抽象方法,让小鸟和管道自己去实现。
3.2 设计单例模式减少对象创建
/// &summary&
/// 小鸟对象单例模式类
/// &/summary&
public class SingleObject
private SingleObject() { }
private static SingleObject singleI
public static SingleObject GetInstance()
if (singleInstance == null)
singleInstance = new SingleObject();
return singleI
public Bird SingleBird
/// &summary&
/// 添加游戏对象
/// &/summary&
/// &param name="parentObject"&游戏对象父类&/param&
public void AddGameObject(GameObject parentObject)
if(parentObject is Bird)
SingleBird = parentObject as B
/// &summary&
/// 绘制游戏对象
/// &/summary&
/// &param name="g"&&/param&
public void DrawGameObject(Graphics g)
SingleBird.Draw(g);
  这里借助单例模式使小鸟实例始终只有一个,实现上主要是将小鸟类和单例模式聚合。
3.3 设计重力辅助类使小鸟能够自动下落
  (1)设计重力辅助类
/// &summary&
/// 重力辅助类
/// &/summary&
public class Gravity
public static float gravity = 9.8f;
/// &summary&
/// s = 1/2*gt^2+vt
/// &/summary&
/// &param name="speed"&速度&/param&
/// &param name="second"&时间&/param&
/// &returns&位移量&/returns&
public static float GetHeight(float speed, float time)
float height = (float)(0.5 * gravity * time * time)
  在Unity游戏引擎中给游戏对象增加一个刚体组件就可以使游戏对象受重力影响,但是在普通的程序中需要自己设计重力类使游戏对象受重力影响下落。这里使用中学物理的知识:求重力加速度的位移量;
  (2)在定时器事件中使小鸟承受重力影响始终下落
private void GravityTimer_Tick(object sender, EventArgs e)
Bird singleBird = SingleObject.GetInstance().SingleB
// Step1:获得小鸟下降的高度
float height = Gravity.GetHeight(singleBird.CurrentSpeed,
singleBird.DurationTime * 0.001f);
// singleBird.DurationTime * 0.001f =& 将毫秒转换成帧
// Step2:获得小鸟下落后的坐标
int y = singleBird.Y + (int)
// Step3:将新Y轴坐标赋给小鸟
int min = this.Size.Height - this.pbxGround.Height
if (y & min)
// 限定小鸟不要落到地面下
singleBird.Y =
// Step4:使小鸟按照加速度下降 [ 公式:v=v0+at ]
singleBird.CurrentSpeed = singleBird.CurrentSpeed
+ Gravity.gravity * singleBird.DurationTime * 0.001f;
  这里重点是将毫秒转换为帧,实现上是使DurationTime*0.001f使速度减慢;
3.4 设计碰撞检测方法使游戏能够终结
  (1)Rectangle的IntersectsWith方法
  在游戏界面中,任何一个游戏对象我们都可以视为一个矩形区域(Rectangle类实例),它的坐标是X轴和Y轴,它还有长度和宽度,可以轻松地确定一个它所在的矩形区域。那么,我们可以通过Rectangle的IntersectsWith方法确定两个Rectangle是否存在重叠,如果有重叠,此方法将返回&true;否则将返回&false。那么,在FlappyBird中主要是判断两种情况:一是小鸟是否飞到边界(屏幕的上方和下方),二是小鸟是否碰到了管道(向上的管道和向下的管道)。
  (2)在定时器事件中循环判断小鸟是否碰到边界或管道
private void PipeTimer_Tick(object sender, EventArgs e)
// 移动管道
this.MovePipeLine();
// 碰撞检测
Bird bird = SingleObject.GetInstance().SingleB
if (bird.Y == 0 || bird.Y == this.pbxGround.Height ||
bird.GetRectangeleArea()
.IntersectsWith(pipeDown.GetRectangeleArea()) ||
bird.GetRectangeleArea()
.IntersectsWith(pipeUp.GetRectangeleArea()))
// 暂停游戏
this.PauseGame();
if (MessageBox.Show("您已挂了,是否购买王胖子的滑板鞋继续畅玩?",
"温馨提示", MessageBoxButtons.YesNo,
MessageBoxIcon.Question) == DialogResult.Yes)
// 重新初始化游戏对象
this.InitialGameObjects();
// 重新开始游戏
this.RestoreGame();
MessageBox.Show("您的选择是明智的,王胖子的滑板鞋太挫了!",
"温馨提示", MessageBoxButtons.OK,
Environment.Exit(0);
四、开发小结
  从运行效果可以看出,此次DEMO主要完成了几个比较核心的内容:一是小鸟和管道的移动,二是小鸟和边界(最上方和最下方以及管道)的碰撞检测。当然,还有很多核心的内容没有实现,比如:计算通过的管道数量、游戏欢迎界面和结束界面等。希望有兴趣的童鞋可以去继续完善实现,这里提供一个我的Flappy Bird实现仅供参考,谢谢!
  赵剑宇,《C#开发史上最虐人游戏-Flappy Bird像素鸟》:
  SimpleFlappyBirdDemo:
阅读(...) 评论()65行 JavaScript 代码实现 Flappy Bird 游戏 - WEB前端 - 伯乐在线
& 65行 JavaScript 代码实现 Flappy Bird 游戏
Flappy Bird 无疑是2014年全世界最受关注的一款游戏。这款游戏是一位来自越南河内的独立游戏开发者阮哈东开发,形式简易但难度极高的休闲游戏,很容易让人上瘾。
这里给大家分享一篇这款游戏的 HTML5 版制作教程,借助
框架,只需65行 JavaScript 代码即可实现。
可能感兴趣的话题
8分不能再高了
关于伯乐前端
伯乐前端分享Web前端开发,包括JavaScript,CSS和HTML5开发技术,前端相关的行业动态。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2017 伯乐在线FlappyBird的风靡,是不是让同学们也有了一个自己制作这个游戏的冲动呢?下来我就把整个游戏的制作流程做一个介绍。如有不足之处还请各位大佬海涵。
首先准备好素材,以下一张图片是我从网上找到的素材图。实际制作中可以根据自己的想法替换成别的更搞笑的素材。
简要流程:
简要代码:
1 window.onload = function () { //页面加载完成
imageLoaded();
5 function imageLoaded () {
var image = new Image();
image.src = 'flappyBird.png';
image.onload = function () { //图片加载完成
var canvas = document.getElementById('canvas');
<span style="color: #
var cxt = canvas.getContext('2d');
<span style="color: #
data.image =
<span style="color: #
drawImage(cxt); //canvas绘图
<span style="color: #
[通过某事件进入GetReady]
<span style="color: #
<span style="color: # }
<span style="color: #
<span style="color: # function getReady () {
<span style="color: #
[单击事件进入gamePlaying]
<span style="color: # }
<span style="color: #
<span style="color: # function gamePlaying () {
<span style="color: #
[失败则进入fail]
<span style="color: # }
<span style="color: #
<span style="color: # function fail () {
<span style="color: #
[通过某事件进入GetReady]
<span style="color: # }
<span style="color: #
<span style="color: # function drawImage (cxt) {
<span style="color: #
[绘制代码]
<span style="color: # }
我们再定义一个全局变量data,游戏中所有的数据都从这里获取或修改,当然在做好游戏后玩的过程中可以通过F12修改其中数值达到外挂的效果~
1 var data = {
image: null, //将图片元素存入,在调用canvas绘制函数时用
score: 0, //当前得分
bestScore: 0, //历史得分,更早获得过的最高分,通过cookie实现
dataRefreshRate: 20, //数据刷新率,游戏是每一个周期改变数据达到运行的目的,用一个统一的值防止混乱
screenRefreshRate: 20, //屏幕刷新率,做第一版的时候没考虑这个问题,结果导致配置低的机子以每20毫秒刷新一次的话(即50hz)会特别卡,所以这版将这个分离出来
start: false, //游戏是否开始
fail: false, //游戏失败与否
<span style="color: #
<span style="color: #
element: { //游戏中的所有元素,其中的show为是否显示,draw为元素对应的绘制函数
<span style="color: #
bird: { //鸟,其实整个游戏可以看作小鸟水平方向静止不动,障碍物向左运动
<span style="color: #
show: true,
<span style="color: #
color: 0, //<span style="color: #黄色,1蓝色,2红色
<span style="color: #
attitude: 0, //飞行姿态,0~2
<span style="color: #
speedY: 0, //下落速度
<span style="color: #
left: 120, //距左边界距离
<span style="color: #
top: 315, //距上边界距离
<span style="color: #
gravity: 0, //重力数值
<span style="color: #
animate: false, //是否运动
<span style="color: #
draw: drawBird
<span style="color: #
<span style="color: #
background: { //背景
<span style="color: #
show: true,
<span style="color: #
type: 0, //<span style="color: #白天,1黑夜
<span style="color: #
draw: drawBackground
<span style="color: #
<span style="color: #
bottomStripe: { //底部滚动条
<span style="color: #
show: true,
<span style="color: #
move: true, //是否移动
<span style="color: #
deviation: 0, //偏移量
<span style="color: #
draw: drawBottomStripe
<span style="color: #
<span style="color: #
obstacle: { //柱子,即障碍物
<span style="color: #
show: false,
<span style="color: #
previousAdopt: 56, //距离上个障碍物的距离
<span style="color: #
adopt: 140, //可通过上下柱之间高度
<span style="color: #
width: 72, //宽度
<span style="color: #
body: [], //用于存储障碍物的队列,重要
<span style="color: #
draw: drawObstacle
<span style="color: #
<span style="color: #
title: { //标题
<span style="color: #
show: true,
<span style="color: #
type: 0, //<span style="color: # flappyBird, 1 Get Ready, 2 Game Over
<span style="color: #
top: 0, //距顶端距离
<span style="color: #
draw: drawTitle
<span style="color: #
<span style="color: #
tip: { //点击提示
<span style="color: #
show: false,
<span style="color: #
draw: drawTip
<span style="color: #
<span style="color: #
startButton: { //开始按钮
<span style="color: #
show: false,
<span style="color: #
draw: drawStartButton
<span style="color: #
<span style="color: #
score: { //分数
<span style="color: #
show: false,
<span style="color: #
value: 0, //分数值
<span style="color: #
draw: drawScore
<span style="color: #
<span style="color: #
rankings: { //分数榜单
<span style="color: #
show: false,
<span style="color: #
top: 600, //距顶端距离
<span style="color: #
draw: drawRankings
<span style="color: #
<span style="color: #
mask: { //黑幕,渐变效果使用
<span style="color: #
show: false,
<span style="color: #
alpha: 0, //透明度
<span style="color: #
draw: drawMask
<span style="color: #
<span style="color: #
<span style="color: #
TIME: {} //setInterval与setTimeout的存储位置
<span style="color: # }
<span style="color: #
例如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. i & 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);
clearInterval(data.TIME.birdAnimate);
绘制小鸟的参数,将素材中的关键点提前写死在函数中,而且随着垂直速度的上升或下降进行旋转,具体实现参考canvas的旋转部分,这里不赘述。
function drawBird (cxt) {
var birdPosition = [
[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
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.
data.element.bird.top = data.element.bird.top + data.element.bird.speedY;
}, data.system.dataRefreshRate);
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. i & 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.
data.element.obstacle.body.push([400, obstacleTop, obstacleBottom]); // data.element.obstacle.body中推入一个数组:[其距离左端的距离, 上端距, 下端距]
for (var i = 0; i & data.element.obstacle.body. 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()) {
其中小鸟可近似看成矩形,满足其存活的条件为:1.不落到地面上,2.不碰到障碍物上。
不落到地面上即data.element.bird.top小于某固定值即可,而不碰到障碍物上即障碍物运动到小鸟的位置时,要保证小鸟的高度在障碍物中间缝隙之间,每次只需要判断当前位置的障碍物是否与小鸟相撞,返回布尔值即可。
function fail其实就比较简单了,其中具体内容为清除之前的一系列Interval,小鸟动作静止,显示分数、排行榜等,及再次回到getReady的按钮,此时完成一个闭环,游戏初步完成。
以下是自己完成度较高的代码,不足之处还望各位大佬指出,共同学习进步。
我参与的团队
积分与排名
评论排行榜
阅读排行榜}

我要回帖

更多关于 flappybird素材下载 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信