一步一步完成坦克大戰:一、游戲關鍵難點實現


寫在前面

  入園這么久以來,一直都是工作中遇到了問題,才在園子里找各位大神的博客看,從來沒有自己寫過。前段時間為了找工作,做了一些面試准備,看了一些書,才發現一直以來都對.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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM