自己动手写游戏:Flappy Bird

雨点打透心脏的1/2处 2021-11-22 18:58 549阅读 0赞

  START:最近闲来无事,看了看一下《C#开发Flappy Bird游戏》的教程,自己也试着做了一下,实现了一个超级简单版(十分简陋)的Flappy Bird,使用的语言是C#,技术采用了快速简单的WindowsForm,图像上主要是采用了GDI+,游戏对象的创建控制上使用了单例模式,现在我就来简单地总结一下。

一、关于Flappy Bird

221424594538524.gif

  《Flappy Bird》是由来自越南的独立游戏开发者Dong Nguyen所开发的作品,游戏中玩家必须控制一只小鸟,跨越由各种不同长度水管所组成的障碍,而这只鸟其实是根本不会飞的……所以玩家每点击一下小鸟就会飞高一点,不点击就会下降,玩家必须控制节奏,拿捏点击屏幕的时间点,让小鸟能在落下的瞬间跳起来,恰好能够通过狭窄的水管缝隙,只要稍一分神,马上就会失败阵亡。简单但不粗糙的8比特像素画面、超级马里奥游戏中的水管、眼神有点呆滞的小鸟和几朵白云,白天夜晚两种模式便构成了游戏的一切。玩家需要不断控制点击屏幕的频率来调节小鸟的飞行高度和降落速度,让小鸟顺利通过画面右方的管道缝隙。如果小鸟不小心擦碰到了管子的话,游戏便宣告结束。

二、游戏设计

2.1 总结游戏印象

  玩过的Flappy Bird的童鞋们应该都对这款游戏有印象,现在我们来看看这款游戏的特点:

  (1)这款游戏的画面很简单:一张背景图,始终就没有变过;

221434311872356.png

  (2)这款游戏的对象只有俩:一个小鸟(有三种挥动翅膀的状态)以及一对管道(有管道向上和向下两个方向);

    小鸟:①221435473905742.gif221436100317825.gif221436153438935.gif

    管道:221437059211983.png

2.2 总结设计思路

  (1)万物皆对象

  在整个游戏中,我们看到的所有内容,我们都可以理解为游戏对象;(在Unity中,GameObject即游戏对象)每一个游戏对象,都由一个单独的类来创建;在游戏中,总共只有两个游戏对象:小鸟和管道,那么我们就可以创建两个类:BirdPipe。但是,我们发现小鸟和管道都有一些共同的属性和方法,例如X,Y轴坐标,长度和宽度,以及绘制(Draw())和移动(Move())的方法,这时我们可以设计一个抽象类,将共有的东西封装起来,减少开发时的冗余代码,提高程序的可扩展性,符合面向对象设计的思路:

221509515004023.jpg

  (2)计划生育好

  在整个游戏中,我们的小鸟对象只有一个,也就是说在内存中只需要存一份即可。这时,我们想到了伟大的计划生育政策,于是我们想到了使用单例模式。借助单例模式,可以保证只生成一个小鸟的实例,即为程序提供一个全局访问点,避免重复创建浪费不必要的内存。

221516269376713.jpg

  (3)对象的运动

  在整个游戏中,小鸟会受重力默认向下坠落,而用户可以根据点击或按键盘Space键使小鸟向上飞,从图像呈现上其本质就是更改游戏对象在Y轴的位置,使其从下往上移动;而管道则会从屏幕右侧出现,从屏幕左侧消失,又从屏幕右侧出现,再从屏幕左侧消失,一直循环往复。可以看到,从图像呈现上期本质就是更改管道对象在X轴的位置,使其从右往左移动。

221526463128324.png

  (4)设计流程图

  在整个开发设计过程中,我们可以根据优先级设计开发流程,根据流程一步一步地实现整个游戏。

221548176715006.jpg

三、关键代码

3.1 设计抽象父类封装共有属性

ContractedBlock.gif ExpandedBlockStart.gif

  1. /// <summary>
  2. /// 游戏对象基类
  3. /// </summary>
  4. public abstract class GameObject
  5. {
  6. #region 01.构造函数及属性
  7. public int X { get; set; }
  8. public int Y { get; set; }
  9. public int Width { get; set; }
  10. public int Height { get; set; }
  11. public GameObject(int x, int y)
  12. {
  13. this.X = x;
  14. this.Y = y;
  15. this.Width = this.Height = 0;
  16. }
  17. public GameObject(int x, int y, int width, int height)
  18. {
  19. this.X = x;
  20. this.Y = y;
  21. this.Width = width;
  22. this.Height = height;
  23. }
  24. #endregion
  25. #region 02.抽象方法
  26. /// <summary>
  27. /// 抽象方法1:绘制自身
  28. /// </summary>
  29. public abstract void Draw(Graphics g);
  30. /// <summary>
  31. /// 抽象方法2:移动自身
  32. /// </summary>
  33. public abstract void Move();
  34. #endregion
  35. #region 03.实例方法
  36. public Rectangle GetRectangeleArea()
  37. {
  38. return new Rectangle(this.X, this.Y, this.Width, this.Height);
  39. }
  40. #endregion
  41. }

  一切皆对象,这里封装了游戏对象小鸟和管道共有的属性,以及两个抽象方法,让小鸟和管道自己去实现。

3.2 设计单例模式减少对象创建

ContractedBlock.gif ExpandedBlockStart.gif

  1. /// <summary>
  2. /// 小鸟对象单例模式类
  3. /// </summary>
  4. public class SingleObject
  5. {
  6. private SingleObject() { }
  7. private static SingleObject singleInstance;
  8. public static SingleObject GetInstance()
  9. {
  10. if (singleInstance == null)
  11. {
  12. singleInstance = new SingleObject();
  13. }
  14. return singleInstance;
  15. }
  16. public Bird SingleBird
  17. {
  18. get;
  19. set;
  20. }
  21. /// <summary>
  22. /// 添加游戏对象
  23. /// </summary>
  24. /// <param name="parentObject">游戏对象父类</param>
  25. public void AddGameObject(GameObject parentObject)
  26. {
  27. if(parentObject is Bird)
  28. {
  29. SingleBird = parentObject as Bird;
  30. }
  31. }
  32. /// <summary>
  33. /// 绘制游戏对象
  34. /// </summary>
  35. /// <param name="g"></param>
  36. public void DrawGameObject(Graphics g)
  37. {
  38. SingleBird.Draw(g);
  39. }
  40. }

  这里借助单例模式使小鸟实例始终只有一个,实现上主要是将小鸟类和单例模式聚合。

3.3 设计重力辅助类使小鸟能够自动下落

  (1)设计重力辅助类

ContractedBlock.gif ExpandedBlockStart.gif

  1. /// <summary>
  2. /// 重力辅助类
  3. /// </summary>
  4. public class Gravity
  5. {
  6. public static float gravity = 9.8f;
  7. /// <summary>
  8. /// s = 1/2*gt^2+vt
  9. /// </summary>
  10. /// <param name="speed">速度</param>
  11. /// <param name="second">时间</param>
  12. /// <returns>位移量</returns>
  13. public static float GetHeight(float speed, float time)
  14. {
  15. float height = (float)(0.5 * gravity * time * time)
  16. + speed * time;
  17. return height;
  18. }
  19. }

  在Unity游戏引擎中给游戏对象增加一个刚体组件就可以使游戏对象受重力影响,但是在普通的程序中需要自己设计重力类使游戏对象受重力影响下落。这里使用中学物理的知识:求重力加速度的位移量;

  (2)在定时器事件中使小鸟承受重力影响始终下落

ContractedBlock.gif ExpandedBlockStart.gif

  1. private void GravityTimer_Tick(object sender, EventArgs e)
  2. {
  3. Bird singleBird = SingleObject.GetInstance().SingleBird;
  4. // Step1:获得小鸟下降的高度
  5. float height = Gravity.GetHeight(singleBird.CurrentSpeed,
  6. singleBird.DurationTime * 0.001f);
  7. // singleBird.DurationTime * 0.001f => 将毫秒转换成帧
  8. // Step2:获得小鸟下落后的坐标
  9. int y = singleBird.Y + (int)height;
  10. // Step3:将新Y轴坐标赋给小鸟
  11. int min = this.Size.Height - this.pbxGround.Height
  12. - 60;
  13. if (y > min)
  14. {
  15. // 限定小鸟不要落到地面下
  16. y = min;
  17. }
  18. singleBird.Y = y;
  19. // Step4:使小鸟按照加速度下降 [ 公式:v=v0+at ]
  20. singleBird.CurrentSpeed = singleBird.CurrentSpeed
  21. + Gravity.gravity * singleBird.DurationTime * 0.001f;
  22. }

  这里重点是将毫秒转换为帧,实现上是使DurationTime*0.001f使速度减慢;

3.4 设计碰撞检测方法使游戏能够终结

  (1)Rectangle的IntersectsWith方法

221618256877947.jpg

  在游戏界面中,任何一个游戏对象我们都可以视为一个矩形区域(Rectangle类实例),它的坐标是X轴和Y轴,它还有长度和宽度,可以轻松地确定一个它所在的矩形区域。那么,我们可以通过Rectangle的IntersectsWith方法确定两个Rectangle是否存在重叠,如果有重叠,此方法将返回 true;否则将返回 false。那么,在FlappyBird中主要是判断两种情况:一是小鸟是否飞到边界(屏幕的上方和下方),二是小鸟是否碰到了管道(向上的管道和向下的管道)。

  (2)在定时器事件中循环判断小鸟是否碰到边界或管道

ContractedBlock.gif ExpandedBlockStart.gif

  1. private void PipeTimer_Tick(object sender, EventArgs e)
  2. {
  3. // 移动管道
  4. this.MovePipeLine();
  5. // 碰撞检测
  6. Bird bird = SingleObject.GetInstance().SingleBird;
  7. if (bird.Y == 0 || bird.Y == this.pbxGround.Height ||
  8. bird.GetRectangeleArea()
  9. .IntersectsWith(pipeDown.GetRectangeleArea()) ||
  10. bird.GetRectangeleArea()
  11. .IntersectsWith(pipeUp.GetRectangeleArea()))
  12. {
  13. // 暂停游戏
  14. this.PauseGame();
  15. if (MessageBox.Show("您已挂了,是否购买王胖子的滑板鞋继续畅玩?",
  16. "温馨提示", MessageBoxButtons.YesNo,
  17. MessageBoxIcon.Question) == DialogResult.Yes)
  18. {
  19. // 重新初始化游戏对象
  20. this.InitialGameObjects();
  21. // 重新开始游戏
  22. this.RestoreGame();
  23. }
  24. else
  25. {
  26. MessageBox.Show("您的选择是明智的,王胖子的滑板鞋太挫了!",
  27. "温馨提示", MessageBoxButtons.OK,
  28. MessageBoxIcon.Information);
  29. Environment.Exit(0);
  30. }
  31. }
  32. }

四、开发小结

221449236565935.gif

  从运行效果可以看出,此次DEMO主要完成了几个比较核心的内容:一是小鸟和管道的移动,二是小鸟和边界(最上方和最下方以及管道)的碰撞检测。当然,还有很多核心的内容没有实现,比如:计算通过的管道数量、游戏欢迎界面和结束界面等。希望有兴趣的童鞋可以去继续完善实现,这里提供一个我的Flappy Bird实现仅供参考,谢谢!

参考资料

  赵剑宇,《C#开发史上最虐人游戏-Flappy Bird像素鸟》:http://bbs.itcast.cn/thread-42245-1-1.html

附件下载

  SimpleFlappyBirdDemo:http://pan.baidu.com/s/1hqtcHIs

作者:周旭龙

出处:http://www.cnblogs.com/edisonchou/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

转载于:https://www.cnblogs.com/edisonchou/p/4115101.html

发表评论

表情:
评论列表 (有 0 条评论,549人围观)

还没有评论,来说两句吧...

相关阅读

    相关 用Unity开发Flappy Bird

    之前上过unity的选修课,学了点java。因为过一阵开发VR还是要unity基础的,于是~ 上网找了教程,找了素材。Siki的教程。 现总结下(我是菜鸟,错误的地方望大

    相关 自己动手ajax框架

    闲来无事就开始琢磨怎么提高网站效率。。。。。然后就对jquery的框架很感兴趣。。 本来很可惜jq没有一个定制的功能,把需要的功能提出来之后不是可以减少很多体积么。 没办法

    相关 自己动手爬虫》笔记

    《自己动手写爬虫》这本书总体介绍了整个网络爬虫由浅入深的知识体系,将爬虫中每个部分分割开来具体的细讲,非常适合新手来入门,由于之前只知道使用爬虫框架,所以一遇到一些错误或者想调