c#版flappybird 未完全实现
这些天开始在深圳找工作,想着把从前有些淡忘的技术再温故下。看到尊敬的《传智播客》有一期公开课,讲的是用c#编写flappybird小游戏,也就自己搜了下游戏资源,也来试试看。
其实用到的技术就是传智播客讲的。只不过项目结构和一些逻辑稍微修改了下,更加符合原游戏的特点。
————————再次声明:非本人独创方案————————————
导入资源
窗体背景设置为background图片382*681,然后在窗体上加个picturebox,放入road图片,大小拉伸.
而后加三个timer,依次设置100ms,10ms,10ms
如此,游戏界面搭建完成。
下面是代码结构,所有游戏元素的父类,抽象类:public abstract class GameObj
小鸟类:public class Bird : GameObj
管道类:public class Pipe : GameObj
重力类:public static class Gravity
GameObj类:添加两个抽象方法void Move() 和 void Draw(Graphics g)
下面是整个类的代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; namespace flappybird { /// <summary> /// 游戏对象的父类 /// </summary> public abstract class GameObj { public int X { get; set; } public int Y { get; set; } public int Height { get; set; } public int Width { get; set; } public GameObj(int x,int y,int height,int width) { this.X = x; this.Y = y; this.Height = height; this.Width = width; } public abstract void Move(); public abstract void Draw(Graphics g); } }
下面是Bird类源码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using flappybird.Properties; namespace flappybird { public class Bird : GameObj { /// <summary> /// 各种小鸟形态数组 /// </summary> private static Image[] imgs ={ Properties.Resources.bird_blue_0, Properties.Resources.bird_blue_1, Properties.Resources.bird_blue_2 }; /// <summary> /// 当前绘制的小鸟形态的索引 /// </summary> public int BirdIndex { get; set; } /// <summary> /// 存储当前下落速度 /// </summary> public float Speed { get; set; } /// <summary> /// 存储已下落持续时间 /// </summary> public float Time { get; set; } private static Bird bird = null; /// <summary> /// 单例模式,确保只实例化一个Bird对象 /// </summary> /// <returns></returns> public static Bird GetSingleBird() { if (bird == null) { bird = new Bird(100, 200, 0); } return bird; } /// <summary> /// 调用父类的构造函数 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="birdIndex"></param> private Bird(int x, int y, int birdIndex) : base(x, y, imgs[0].Height, imgs[0].Width) { this.BirdIndex = birdIndex; this.Time = 10f; this.Speed = 0f; } /// <summary> /// 重写父类的抽象Draw方法 /// </summary> public override void Draw(Graphics g) { switch (this.BirdIndex) { case 0: g.DrawImage(imgs[0], this.X, this.Y); break; case 1: g.DrawImage(imgs[1], this.X, this.Y); break; case 2: g.DrawImage(imgs[2], this.X, this.Y); break; default: break; } this.BirdIndex++; this.BirdIndex %= 3; } /// <summary> /// 重写父类的抽象Move方法 /// </summary> public override void Move() { this.Y = this.Y <= 60 ? 0 : this.Y - 60; //this.Y -= 30; } /// <summary> /// 获取图形区域,用于碰撞检测 /// </summary> /// <returns></returns> public static Rectangle GetRectangle() { return new Rectangle(bird.X, bird.Y, Resources.bird_blue_0.Width, Resources.bird_blue_0.Height); } } }
结合注释,相信大家都能搞懂。。。
Pipe类:
此类中,我定义了一个pipes数组,用来向画面中呈现3列水管(每列包含上下两个水管)
然后用InitPipes()函数初始化。此函数中,用了一种比较讨巧的方法,就是先判断数组中最后一个元素是否为null,类似Bird类中使用的单例模式。
具体代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using flappybird.Properties; namespace flappybird { public class Pipe : GameObj { public override void Move() { for (int i = 0; i < 3; i++) { pipes[i, 0].X -= 1; pipes[i, 1].X -= 1; } // this.X -= 1; } public override void Draw(System.Drawing.Graphics g) { for (int i = 0; i < 3; i++) { //画出上水管 g.DrawImage(Resources.bound_up, pipes[i, 0].X, pipes[i, 0].Y); //画出下水管 g.DrawImage(Resources.bound_down, pipes[i, 1].X, pipes[i, 1].Y); } } private Pipe(int x, int y) : base(x, y, Resources.bound_down.Height, Resources.bound_down.Width) { } private static Pipe[,] pipes = new Pipe[3, 2]; private static void InitPipes() { if (pipes[2, 1] == null) { Random rand = new Random(DateTime.Now.Millisecond); for (int i = 0; i < 3; i++) { int y = rand.Next(80, 380); //上水管 pipes[i, 0] = new Pipe(300 + i * 200, -y); //下水管 pipes[i, 1] = new Pipe(300 + i * 200, -y + Resources.bound_up.Height + 170); } } } public static Pipe GetPipes(int row, int col) { if (row > 1 || col > 2) { return null; } InitPipes(); return pipes[col, row]; } public static Rectangle GetRectangle(int row,int col) { return new Rectangle(pipes[col, row].X, pipes[col, row].Y, Resources.bound_up.Width, Resources.bound_up.Height); } } }
然后Gravity类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace flappybird { public static class Gravity { private const float g=9.8f; private const float a = 40f; /// <summary> /// 获取下落的距离 /// </summary> /// <param name="time"></param> /// <param name="speed"></param> /// <returns></returns> public static int GetDropHeight(float time,float speed) { if (speed<0) { return (int)(0.5f * a * time * time + speed * time); } return (int)(0.5f * g * time * time + speed * time); } /// <summary> /// 获取下落后的速度 /// </summary> /// <param name="time"></param> /// <param name="speed"></param> /// <returns></returns> public static float GetDropSpeed(float time,float speed) { if (speed<0) { return speed + a * time; } return speed + g * time; } } }
定义一个 float a;这个表示小鸟向上飞的时候受到的向下的加速度。之所以这样设计,是为了小鸟向上飞的时候,不是一下就跳到某个高度,让它连续上升,但是减速特快的那种效果。
我看传智播客里赵老师是直接设置个上升高度,给人的感觉是小鸟一下就”飞“上去了,这样设计会简单些,缺点是没有连续效果。
大餐来了,Form1代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using flappybird.Properties; namespace flappybird { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private bool IsGameOver = false; private void Form1_Load(object sender, EventArgs e) { timer1.Enabled = true; timer2.Enabled = true; timer3.Enabled = true; } private void Form1_Paint(object sender, PaintEventArgs e) { if (IsGameOver) { return; } Bird.GetSingleBird().Draw(e.Graphics); Pipe.GetPipes(0, 0).Draw(e.Graphics); } private void timer1_Tick(object sender, EventArgs e) { if (IsGameOver) { return; } this.Invalidate(); } private void Form1_MouseClick(object sender, MouseEventArgs e) { if (IsGameOver) { return; } //Bird.GetSingleBird().Move(); Bird.GetSingleBird().Speed = -80f; } private void Form1_KeyPress(object sender, KeyPressEventArgs e) { } private void Form1_KeyDown(object sender, KeyEventArgs e) { if (IsGameOver) { return; } if (e.KeyCode == Keys.Space) { //Bird.GetSingleBird().Move(); Bird.GetSingleBird().Speed = -80f; } } /// <summary> /// 计算重力影响 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void timer2_Tick(object sender, EventArgs e) { if (IsGameOver) { return; } int dropHeight = Gravity.GetDropHeight(Bird.GetSingleBird().Time * 0.01f, Bird.GetSingleBird().Speed); int currentY = Bird.GetSingleBird().Y + dropHeight; int maxY = ClientRectangle.Height - pictureBox1.Height - Bird.GetSingleBird().Height; if (currentY >= maxY) { Bird.GetSingleBird().Y = maxY; } else if (currentY < 0) { Bird.GetSingleBird().Y = 0; } else { Bird.GetSingleBird().Y = currentY; } Bird.GetSingleBird().Speed = Gravity.GetDropSpeed(Bird.GetSingleBird().Time * 0.01f, Bird.GetSingleBird().Speed); } Random rand = new Random(DateTime.Now.Millisecond); private void timer3_Tick(object sender, EventArgs e) { if (IsGameOver) { return; } Pipe.GetPipes(0, 0).Move(); //最靠近左边的管道列,为了下面的碰撞检测做准备。 //因为只有最左边的管道才需要碰撞检测。避免不必要的操作。 int left = 0; for (int i = 0; i < 3; i++) { //若某一列管道左移出了屏幕范围,则把其放到屏幕右端外 //然后再进行左移 if (Pipe.GetPipes(0, i).X < -90) { //若i列移出了屏幕,则现在是(i + 1) % 3列在最左侧 left = (i + 1) % 3; Console.WriteLine(left); int y = rand.Next(80, 380); //上面的管道 Pipe.GetPipes(0, i).X = 500; Pipe.GetPipes(0, i).Y = -y; //下面的管道 Pipe.GetPipes(1, i).X = 500; Pipe.GetPipes(1, i).Y = -y + Resources.bound_up.Height + 170; } } bool isInterUp = Bird.GetRectangle().IntersectsWith(Pipe.GetRectangle(0, left)); bool isInterDown = Bird.GetRectangle().IntersectsWith(Pipe.GetRectangle(1, left)); bool isInterGround = Bird.GetRectangle().IntersectsWith(new Rectangle(pictureBox1.Location, pictureBox1.Size)); if (isInterUp||isInterDown||isInterGround) { timer1.Enabled = false; timer2.Enabled = false; timer3.Enabled = false; IsGameOver = true; } } } }
结合前面的几个类,这里就留给大家自己体会吧。(绝不是我懒得写说明。哈哈哈)