首页 > 代码库 > c#版flappybird 未完全实现

c#版flappybird 未完全实现

这些天开始在深圳找工作,想着把从前有些淡忘的技术再温故下。看到尊敬的《传智播客》有一期公开课,讲的是用c#编写flappybird小游戏,也就自己搜了下游戏资源,也来试试看。

其实用到的技术就是传智播客讲的。只不过项目结构和一些逻辑稍微修改了下,更加符合原游戏的特点。

————————再次声明:非本人独创方案————————————

导入资源

QQ截图20141208172348

窗体背景设置为background图片382*681,然后在窗体上加个picturebox,放入road图片,大小拉伸.

QQ截图20141208172705

而后加三个timer,依次设置100ms,10ms,10ms

QQ截图20141213110101

如此,游戏界面搭建完成。

下面是代码结构,所有游戏元素的父类,抽象类: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;            }        }    }}

结合前面的几个类,这里就留给大家自己体会吧。(绝不是我懒得写说明。哈哈哈)

QQ截图20141213142407

传送门:http://download.csdn.net/detail/geeking/8255401

c#版flappybird 未完全实现