C# WinForm 开发游戏——小鸡快跑

首先,了解下WinForm做游戏的基本思路:

做游戏需要的最基本的两个元素,一个是屏幕,另一个就是在屏幕的移动的对象了。

然后,了解下parint事件,WinForm的对象都是继承至Control类的,而Control类中包含一个事件PaintEventHandler Paint,paint翻译过来就是喷绘,类似于绘画,当容器刷新时,就等于重新喷绘一次图像,就会触发此事件。

有了这些,就可以开始做游戏了。

先是定义一个元素(本文是小鸡),这个元素包含一张图片,和X坐标和Y坐标,然后将元素按其坐标,添加进屏幕(WinForm窗口或者其他容器,本文使用PictureBox)中,这样就屏幕就会在刚才定义的X坐标和Y坐标处,出现一个元素的图像。

然后,定义一个定时器timer,每30毫秒运行一次,每次运行都要刷新屏幕。自然屏幕刷新就会触发paint事件啦,本文中的paint事件为GamepictureBox_Paint

那么怎么移动小鸡呢?很简单,在定时器timer的事件里(本文为timer1_Tick)将元素的X坐标改变一下就可以了,然后timer里会进行容器刷新,容器刷新就会触发

paint事件,然后在paint事件里,重新定位下小鸡的X坐标就行了。

不多说了,上代码。

Form页面代码如下:

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 Chicken.Properties;

namespace Chicken
{
    public partial class MyG : Form
    {
        Element Chicken;//小鸡类
        Road GameRoad;//陆块类
        public int RoadCount;//陆块数
        public int Length;//陆块长度
        int EndX;//设置终点X
        EventHandler TimerHandler;//时间控制手柄
        bool TimerHandlerbool;//是否已传递时间手柄
        EventHandler AgainGame;//时间控制手柄
        int GamePicX;
        int GamePicY;
        public MyG()
        {
            InitializeComponent();

            Initial(20, Resources.Bird.Width + 10);//陆块长度为小鸡长度加10 50个陆块
        }

        private void Initial(int Rcount, int Len)
        {
            AgainGame += new EventHandler(AgainGame_Start);//实例化重新开始手柄
            TimerHandler += new EventHandler(Timer_Enabled);//实例化时间手柄
            RoadCount = Rcount;//陆块数
            Length = Len;//陆块长度
            TimerHandlerbool = false;//未已传递时间手柄
            Chicken = new Element(0, 100-Resources.Bird.Height);
            GameRoad = new Road(RoadCount, Len);
            GamePicX = 0;
            GamePicY = 0;
            Point p = new Point();
            p.Offset(GamePicX, GamePicY);
            GamepictureBox.Location = p;

        }
        private void InitialLand(Graphics g)
        {
            //Pen pen = new Pen(Color.Green);
            for (int i = 0; i < GameRoad.ListRoad.Count; i++)
            {
                RoadItem Item = GameRoad.ListRoad[i];
                if (Item.type == 1)//如果类型为1 是陆块是陆地
                {

                    Image img = GameRoad.LandImgList[Item.imageIndex];

                    g.DrawImage(img,
                                    new Rectangle(
                                            Item.start.X,
                                            Item.end.Y,
                                            Item.end.X - Item.start.X,
                                            img.Height
                                            )
                               );//画陆块
                }
            }
            EndX = GameRoad.ListRoad.ElementAt(RoadCount - 1).end.X;//设置终点X
            this.GamepictureBox.Width = EndX;
        }
        /// <summary>
        /// 时间控制函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Timer_Enabled(object sender, EventArgs e)
        {
            TimerHandler -= new EventHandler(Timer_Enabled);
            timer1.Enabled = false;
            Dead D = new Dead(AgainGame);
            D.Show();
        }
        /// <summary>
        /// 游戏开始控制函数
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AgainGame_Start(object sender, EventArgs e)
        {
            AgainGame -= new EventHandler(AgainGame_Start);
            Initial(RoadCount, Length);
            timer1.Enabled = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            //设置屏幕移动
            if ((Chicken.x + this.GamepictureBox.Location.X) > this.Width / 2 &&
                (this.GamepictureBox.Width + this.GamepictureBox.Location.X) > this.Width)
            {
                int OffX = 1;

                if (Chicken.IsSpeedUp)
                {
                    OffX = 2;
                }
                GamePicX = GamePicX - OffX;

                Point p = GamepictureBox.Location;

                p.Offset(GamePicX, GamePicY);
                GamepictureBox.Location = p;




            }

            if (Chicken.x + Chicken.bmp.Width / 2 >= EndX)
            {
                timer1.Enabled = false;
                Replay R = new Replay(AgainGame);
                R.Show();

            }
            int CurrentRoadsIndex = Chicken.x / Length;//获取当前为第几个陆块
            if (CurrentRoadsIndex >= RoadCount) { CurrentRoadsIndex = RoadCount - 1; }//如果大于定义总陆块数 设置为最大数
            if (CurrentRoadsIndex < 0) { CurrentRoadsIndex = 0; }
            if (GameRoad.ListRoad.ElementAt(CurrentRoadsIndex).type == 0)//如果当前陆块为空 
            {
                // Y坐标等于空陆块Y坐标
                if ((Chicken.y + Chicken.bmp.Height) == GameRoad.ListRoad.ElementAt(CurrentRoadsIndex).start.Y)
                {
                    int DepthEndX = GameRoad.ListRoad.ElementAt(CurrentRoadsIndex).end.X;//X下落点为当前陆块的X
                    if (CurrentRoadsIndex + 1 <= RoadCount - 1)//如果下一个陆块存在
                    {
                        if (GameRoad.ListRoad.ElementAt(CurrentRoadsIndex + 1).type == 0)//如果下一个陆块也是空
                        {
                            DepthEndX = GameRoad.ListRoad.ElementAt(CurrentRoadsIndex + 1).end.X;//X下落点为下一个陆块的X
                        }
                    }
                    if (Chicken.x + Chicken.bmp.Width < DepthEndX)//对象的坐标加对象的宽度 小于空陆块的尾坐标
                    {

                        Chicken.IsFalling = true;//下降
                        if (!TimerHandlerbool)
                        {
                            Chicken.GetHandler(TimerHandler);//传递时间控制手柄
                            TimerHandlerbool = true;

                        }
                    }

                }

            }
            GamepictureBox.Refresh();

        }

        private void GamepictureBox_Paint(object sender, PaintEventArgs e)
        {
            Chicken.Draw(e.Graphics);
            InitialLand(e.Graphics);
        }

        private void MyG_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Right)
            { Chicken.IsRuning = true; }
            if (e.KeyData == Keys.Space && Chicken.IsRuning)
            { Chicken.IsSpeedUp = true; }
            if (e.KeyData == Keys.Up)
            { Chicken.IsJumping = true; }
            int CurrentRoadsIndex = Chicken.point.X / Length;//当前陆块
            if (e.KeyData == Keys.Left)
            { Chicken.Back = true; }

        }

        private void MyG_KeyUp(object sender, KeyEventArgs e)
        {
            Chicken.IsRuning = false;
            Chicken.IsSpeedUp = false;
            Chicken.Back = false;
        }

        private void GamepictureBox_MouseDown(object sender, MouseEventArgs e)
        {
            Chicken.x = e.X;
            Chicken.y = e.Y;
        }
    }
}

 

元素类,定义几个变量来控制对象,注释还算比较多,就不一一解释了,如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Chicken.Properties;

namespace Chicken
{
    class Element
    {
        public int x; 
        public int y;
        public int JumpHeight = 0;//跳跃高度 
        private bool JumpTop = false;//是否跳到最高点
        public int FallHeight = 0;//跳跃高度 
        public bool FallDepth = false;//是否跳到最高点
        public int BasicSpeed = 1;//基本速度
        public bool IsRuning = false;//是否移动
        public bool Back = false;//是否后退
        public bool IsJumping = false;//是否跳跃   
        public bool IsSpeedUp = false;//是否加速
        public bool IsFalling = false;//是否降落
        public Image bmp;//对象图形
        public Image img;//对象图形 暂不使用
        public Point point;//坐标 暂不使用
        public EventHandler TimerHandler;
        public Element(int x, int y)
        {
            bmp = Resources.Bird;
            this.x = x;
            this.y = y;
        }
        public Element(int x,int y,Image img)
        {
            bmp = Resources.Bird;
            this.x = x;
            this.y = y;
            this.img = img;
        }
        public void Draw(Graphics G)
        {
            G.DrawImage(bmp, x, y);
            Move();
        }
        public void Move()
        {
            if (IsFalling)
            {
                IsSpeedUp = false;
                IsJumping = false;
                IsRuning = false;
                if (!FallDepth)
                {
                    this.y += BasicSpeed * 2;
                    FallHeight += BasicSpeed * 2;
                }
                if (FallHeight == 50) 
                { 
                    FallDepth = true; 
                    IsFalling = false;
                    TimerHandler(null, null);
                    
                }//如果下降了50 则下降完成 不在下降
              
                
            }
            if (Back)
            {
                bmp = Resources.BirdBack;
                this.x -= BasicSpeed;
                
            }
            if (IsSpeedUp)
            {
                bmp = Resources.Bird;
                this.x += BasicSpeed*3;
            }
            else if (IsRuning)
            {
                bmp = Resources.Bird;
                this.x +=  BasicSpeed;
            }
            if (IsJumping)
            {
                if (!JumpTop)
                {
                    this.y += BasicSpeed * (-2);
                    JumpHeight += BasicSpeed * (2);
                }
                else
                {
                    this.y += BasicSpeed * 2;
                    JumpHeight += BasicSpeed * (-2);
                }
                if (JumpHeight == 30) { JumpTop = true; }//如果跳跃了30 则跳跃到顶部 不在上升
                if (JumpHeight == 0) { JumpTop = false; IsJumping = false; }//如果回到地面 不在下降 跳跃结束
            }
           
        }
        public void GetHandler(EventHandler TimerHandler)
        {
            this.TimerHandler = TimerHandler;
        }

    }
}

然后,创建陆块类,如下:

using System;
using System.Collections.Generic;
 
using System.Text;
using System.Drawing;
using Chicken.Properties;

namespace Chicken
{
    class Road
    {
        private Random rand = new Random();
       
        public List<Image> LandImgList = new List<Image>();
        public List<RoadItem> ListRoad = new List<RoadItem>();
        public int RoadY = 100;//陆地的Y坐标
        /// <summary>
        /// 构建陆地
        /// </summary>
        /// <param name="Number">陆块数量 必须大于2</param>
        /// <param name="Length">陆块长度</param>
        public Road(int Number, int  Length)
        {
            if (Length < 2)
                return;
            
            RoadItem StartItem = new RoadItem(0, Length);
            StartItem.imageIndex = 0;//选择陆块的图像 0为第一个平地 1为第二个左倾斜 2为第三个右倾斜
            StartItem.type = 1;
            ListRoad.Add(StartItem);//先添加一个陆块 第一个路块必须是陆地
           
            for (int i = 0; i < Number - 2; i++)
            {
                int Temp = rand.Next(0, 3);
                int Index = 0;//选择陆块的图像 0为第一个平地 1为第二个左倾斜 2为第三个右倾斜 这里暂时不使用
                int Ang = 0;
                if (Temp == 0)
                {
                    Ang = -20;
                    Index = 2;
                }
                else if (Temp == 1)
                {
                    Ang = 0;
                    Index = 0;
                }
                else
                {
                    Ang = 20;
                    Index = 1;
                }
                RoadItem CItem = new RoadItem(Ang, Length);
                //CItem.imageIndex = Index;获取随机陆块的图片 这样获取Y值需要写一个一元一次方程获取
                CItem.imageIndex = 0;//这里设置全为第一个图像 这样获取Y值比较方便
                if (rand.Next(0, 4) == 1)//4分之1的可能性为空陆块
                    CItem.type = 0;
                else
                    CItem.type = 1;

                ListRoad.Add(CItem);//添加中间的陆块 添加进陆块列表
            }

            RoadItem EndItem = new RoadItem(0, Length);
            EndItem.imageIndex = 0;//选择陆块的图像 0为第一个平地 1为第二个左倾斜 2为第三个右倾斜 
            EndItem.type = 1;
            ListRoad.Add(EndItem);//添加最后一个陆块

            for (int i = 0; i < ListRoad.Count; i++)
            {
                RoadItem DrawItem = ListRoad[i];
                if (i == 0)
                { DrawItem.start = new Point(0, RoadY); }
                else
                { DrawItem.start = ListRoad[i - 1].end; }

                DrawItem.end = new Point(DrawItem.start.X + DrawItem.length, RoadY);
            }
            //为每一个陆块 定义 起始和终止向量坐标

           
            LandImgList.Add(Resources.land);
         
           //为陆块使用的图片列表 赋值
        }
    }
    
    public class RoadItem
    {
        public int angle;
        public int length;//陆块长度
        public int type;//0为空,1为陆地
        public int imageIndex = 0;//使用的图片
        /// <summary>
        /// 构建路块
        /// </summary>
        /// <param name="angle"></param>
        /// <param name="length">陆块长度</param>
        public RoadItem(int angle, int length)
        {
            this.angle = angle;
            this.length = length;
        }
        public Point start;//陆块起始坐标
        public Point end;//陆块终止坐标
       
     
    }
}

辅助类,这两个类是两个窗口,我闲MessageBox不太好看,就换了个窗口,但貌似也没好看到那里去。。。哈哈

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;

namespace Chicken
{
    public partial class Replay : Form
    {
         EventHandler Again;
         public Replay(EventHandler Again)
        {
            this.Again = Again;
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Close();
            Again(null, null);
        }
    }
}
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;

namespace Chicken
{
    public partial class Dead : Form
    {
        EventHandler Again;
        public Dead(EventHandler Again)
        {
            this.Again = Again;
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Close();
            Again(null, null);
        }
    }
}

这两个类是死亡窗口和重新开始窗口。
源代码下载地址http://download.csdn.net/detail/kiba518/4355712

源代码中,和文中的代码稍微有点不一样,如果我记得没错是这里,如下:

 if (Chicken.x + Chicken.bmp.Width / 2 >= EndX)

是修改,如果鸡身的一半以上超过终点,到达终点,游戏结束。这个源码上传时没修改这里。

不过不影响运行啦,但是还有一些小BUG。。

如果想升级这个游戏也很简答,比如,定义一个炮弹类,随机发一个。

当炮弹的矩形和小鸡的矩形相碰撞了,就死亡啦,矩形相撞有函数的,有兴趣的朋友可以自己扩展。

补充:上是跳跃,左右可以移动,空格是加速,鼠标全屏飞。。。。

开发环境:VS2008。

代码很简单,可以复制到别的环境中运行。

 

上一篇:iOS description与debugDescription在调试程序中的应用


下一篇:《Cocos2d 跨平台游戏开发指南(第2版)》一1.1 内容简介