一步一步完成坦克大战:一、游戏关键难点实现

写在前面

  入园这么久以来,一直都是工作中遇到了问题,才在园子里找各位大神的博客看,从来没有自己写过。前段时间为了找工作,做了一些面试准备,看了一些书,才发现一直以来都对.NET的基础知识掌握的不够熟练,会导致很多问题。所以就想着向园子里的各位大佬学习,从写博客开始加强自己对基础知识的巩固,也以此来督促自己要不断的学习和总结。

  为什么要写坦克大战呢?原因还是因为坦克大战非常适合面向对象编程,结合自己这段时间看《C#图解教程》这本书的理解,可以做到一个很好的项目实践,加深自己对类,类的构造函数、属性,类的继承,抽象类,虚方法,方法的重载等等基础知识的运用。

游戏实现思路

  坦克大战的实现思路还是比较好想到的,大概可以分为3个步骤

  1、可以通过绘图片的方式,在一个画布上根据坐标绘制我们要的元素,这些元素包括:玩家坦克、敌方坦克,玩家子弹,敌方子弹,道具,砖墙,草地等。

  2、对于元素的动作,这些动作包括:玩家移动,敌方坦克移动,玩家子弹移动,敌方子弹移动,砖墙爆炸等等。可以设置一个定时刷新的时间,在这个时间内完成所有的动作,完成之后每个元素都产生新的坐标数据。

  3、根据新的坐标数据在画布中绘制所有元素

  游戏中的主要问题其实就集中在如何绘制元素,如何实现元素的动作(比如移动,射击,爆炸等),在本文中会先用面向过程的方式实现这些关键问题,后续再从面向对象的方式来实现游戏。

关键难点Demo实现

一、准备工作

  • 游戏资源 。可以自己网上找找,也可以从我后续的源码中获取
  • 需要具备一些.NET  Winform 开发的基础知识
  • 需要了解GDI编程的基础用法

二、绘制元素

  1、首先创建一个项目TankWar.TestDemo,在vs中调整好窗口大小,黑色背景,大概就是下面这样

  

  2、导入游戏资源:右键项目=>属性=》资源=》添加资源,把下载好的游戏资源全都导入进来,大概就是下面这样

  

  3、绘制一个坦克图片

  在窗体中初始化好我们需要的坦克图片,然后在窗体的Paint事件中添加绘制代码

  

        //玩家坦克图片
        private static Image[] imgs_play = {
            Resources.p1tankU,
            Resources.p1tankD,
            Resources.p1tankL,
            Resources.p1tankR,
                               };

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 窗体绘制事件
        /// </summary>
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.DrawImage(imgs_play[0], 0, 0);//绘制图片到(0,0)位置,(0,0)对应的是图片imgs[0]的左上角
        }
View Code

   运行效果大概是下面这样:

  

 

  通过同样的方式我们可以在窗体中的不同坐标上绘制敌方坦克,子弹等等,第一个关键点绘制图片就完成了。

 

三、元素的动作

3.1坦克移动

  根据实现思路,这里我们需要给窗体设置一个刷新时间,然后在这个时间内完成坦克的移动,再重新绘制坦克

  1. 首先添加一个timer控件到窗体中,并添加timer控件的事件,并在窗体构造函数中初始化timer
  2. 然后为窗体添加KeyDown事件,这里我对方向的定义为:W=》上,S=》下,D=》右,A=》左

  假设每次坦克移动10个坐标,具体的代码实现如下:

public partial class Form1 : Form
    {
        //玩家坦克图片
        private static Image[] imgs_play = {
            Resources.p1tankU,
            Resources.p1tankD,
            Resources.p1tankL,
            Resources.p1tankR,
                               };

        private int p_speed = 10;//玩家速度
        private int p_x = 0;//玩家x坐标
        private int p_y = 0;//玩家y坐标
        private int p_direction = 0;//玩家方向  0向上,1向下,2向左,3向右

        public Form1()
        {
            InitializeComponent();
            //初始化timer
            this.timer1.Interval = 50;
            this.timer1.Enabled = true;
        }

        /// <summary>
        /// 窗体绘制事件
        /// </summary>
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            //g.DrawImage(imgs_play[0], 0, 0);//绘制图片到(0,0)位置,(0,0)对应的是图片imgs[0]的左上角
            g.DrawImage(imgs_play[p_direction], p_x, p_y);
        }

        /// <summary>
        /// timer事件
        /// </summary>
        private void timer1_Tick(object sender, EventArgs e)
        {
            // 对窗体进行重新绘制
            this.Invalidate();
        }

        /// <summary>
        /// 按下按键
        /// </summary>
        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.W:
                    p_direction = 0;
                    p_y = p_y - p_speed;
                    break;
                case Keys.S:
                    p_direction = 1;
                    p_y = p_y + p_speed;
                    break;
                case Keys.A:
                    p_direction = 2;
                    p_x = p_x - p_speed;
                    break;
                case Keys.D:
                    p_direction = 3;
                    p_x = p_x + p_speed;
                    break;
            }
        }
    }
View Code

  以上代码就能让我们的坦克移动起来了

3.2坦克射击

  当玩家按下K的时候,坦克射击子弹

  1. 初始化子弹的图片、子弹速度、和存储子弹的列表
  2. 添加一个发射子弹的方法,在KeyDown事件中按下K时,执行该方法
  3. 在timer事件中移动子弹,在窗体重绘实践中绘制子弹
public partial class Form1 : Form
    {
        //玩家坦克图片
        private static Image[] imgs_play = {
            Resources.p1tankU,
            Resources.p1tankD,
            Resources.p1tankL,
            Resources.p1tankR,
                               };

        private int p_speed = 10;//玩家速度
        private int p_x = 0;//玩家x坐标
        private int p_y = 0;//玩家y坐标
        private int p_width = 60;//玩家图片宽度
        private int p_height = 60;//玩家图片高度
        private int p_direction = 0;//玩家方向  0向上,1向下,2向左,3向右

        private static Image imgs_pbullet = Resources.tankmissile;//子弹图片
        private int b_width = 17;//子弹图片宽度
        private int b_height = 17;//子弹图片高度
        private int b_speed = 15;//子弹速度(最好比paly大)
        private List<int[]> listBullet = new List<int[]>();//存放画布中的子弹
        

        public Form1()
        {
            InitializeComponent();
            //初始化timer
            this.timer1.Interval = 50;
            this.timer1.Enabled = true;
        }

        /// <summary>
        /// 窗体绘制事件
        /// </summary>
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            //g.DrawImage(imgs_play[0], 0, 0);//绘制图片到(0,0)位置,(0,0)对应的是图片imgs[0]的左上角
            //绘制玩家坦克
            g.DrawImage(imgs_play[p_direction], p_x, p_y);

            //绘制子弹
            DrawPlayBullet(g);
        }

        /// <summary>
        /// timer事件
        /// </summary>
        private void timer1_Tick(object sender, EventArgs e)
        {
            //每次重绘就移动子弹
            PlayBulletMove();
            // 对窗体进行重新绘制
            this.Invalidate();
        }

        /// <summary>
        /// 按下按键
        /// </summary>
        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.W:
                    p_direction = 0;
                    p_y = p_y - p_speed;
                    break;
                case Keys.S:
                    p_direction = 1;
                    p_y = p_y + p_speed;
                    break;
                case Keys.A:
                    p_direction = 2;
                    p_x = p_x - p_speed;
                    break;
                case Keys.D:
                    p_direction = 3;
                    p_x = p_x + p_speed;
                    break;
                case Keys.K:
                    AddPlayBullet(p_direction, p_x, p_y);
                    break;
            }
        }

        /// <summary>
        /// 添加玩家子弹
        /// </summary>
        /// <param name="b_direction">子弹方向</param>
        /// <param name="p_x">玩家的x坐标</param>
        /// <param name="p_y">玩家的y坐标</param>
        private void AddPlayBullet(int b_direction, int p_x, int p_y)
        {
            int b_x = p_x;
            int b_y = p_y;
            //因为图片的大小,玩家方向,需要调整下子弹的位置,让其再玩家前正中方
            switch (b_direction)
            {
                case 0:
                    b_x += (int)(Math.Abs(b_width - p_width) / 2);
                    b_y -= imgs_pbullet.Height;
                    break;
                case 1:
                    b_x += (int)(Math.Abs(b_width - p_width) / 2);
                    b_y += imgs_play[b_direction].Height;
                    break;
                case 2:
                    b_x -= imgs_pbullet.Width;
                    b_y += (int)(Math.Abs(b_height - p_height) / 2);
                    break;
                case 3:
                    b_x += imgs_play[b_direction].Width;
                    b_y += (int)(Math.Abs(b_height - p_height) / 2);
                    break;
            }
            int[] _b = new int[4] { b_direction, b_x, b_y, b_speed };
            listBullet.Add(_b);
        }

        /// <summary>
        /// 绘制玩家子弹
        /// </summary>
        /// <param name="g"></param>
        private void DrawPlayBullet(Graphics g)
        {
            foreach (var item in listBullet)
            {
                g.DrawImage(imgs_pbullet, item[1], item[2]);
            }
        }

        /// <summary>
        /// 移动子弹
        /// </summary>
        private void PlayBulletMove()
        {
            foreach (var item in listBullet)
            {
                switch (item[0])
                {
                    case 0:
                        item[2] -= item[3];
                        break;
                    case 1:
                        item[2] += item[3];
                        break;
                    case 2:
                        item[1] -= item[3];
                        break;
                    case 3:
                        item[1] += item[3];
                        break;
                    default:
                        break;
                }
            }
        }
    }
View Code

  运行后的效果图如下:

    

3.3坦克的碰撞

    坦克的碰撞可以看作是两个矩形图片存在了交集,我们可以使用Rectangle的IntersectsWith方法来判断两个矩形是否有交集。

  

    接下来我们按照上面3.1,3.2的方式绘制一些敌方坦克,然后在timer事件中执行碰撞的检测,并执行绘制爆炸效果

    代码稍微比较长,这里直接上效果图

    

 

到这里,坦克大战游戏的关键难点就基本实现了,后面将会用面向对象的方式来实现整个游戏

源码下载

本文的内容对应解决方案中的TankWar.TestDemo项目

Github:https://github.com/Okarlchen/TankWar

 

posted @ 2018-12-01 18:56  Karl Chen  阅读(1666)  评论(0编辑  收藏  举报