自己動手寫游戲:坦克撕逼大戰


START:最近在公交車上無聊,於是用平板看了看下載的坦克大戰的開發教程,於是在晚上回家后花了兩天模仿了一個,現在來總結一下。

一、關於坦克大戰

  《坦克大戰》(Battle City)是1985年日本南夢宮Namco游戲公司開發並且在任天堂FC平台上,推出的一款多方位平面射擊游戲。游戲以坦克戰斗及保衛基地為主題,屬於策略型聯機類。同時也是FC平台上少有的內建關卡編輯器的幾個游戲之一,玩家可自己創建獨特的關卡,並通過獲取一些道具使坦克和基地得到強化。

  1985年推出的坦克大戰(Battle City)由13×13大小的地圖組成了35個關卡,地形包括磚牆、海水、鋼板、森林、地板5種,玩家作為坦克軍團僅存的一支精銳部隊的指揮官,為了保衛基地不被摧毀而展開戰斗。游戲中可以獲取有多種功能的寶物,敵人種類則包括裝甲車、輕型坦克、反坦克炮、重型坦克4種,且存在炮彈互相抵消和友軍火力誤傷的設定。

  1990年推出的坦克大戰較原版而言,可以選擇14種規則進行游戲(Tank A-Tank N),且敵方坦克增加了護甲,也能通過寶物讓我方陷入不利局面。寶物當中增加了能通過海水或樹林的特性。全部關卡為50關。

二、關於游戲設計

2.1 總結游戲印象

  我相信坦克大戰一定是大部分80后童鞋兒時的經典,現在我們拉看看這款游戲的經典之處:

  (1)一個玩家坦克,多個電腦坦克

  ①   ②   ③   ④

  (2)玩家可以發子彈,電腦坦克也可以發子彈

  ①  ②

  (3)電腦坦克被擊中后有爆炸效果,並且有一定幾率出現游戲道具

  ①  ②  ③

2.2 總結設計思路

  (1)萬物皆對象

  在整個游戲中,我們看到的所有內容,我們都可以理解為游戲對象(GameObject),每一個游戲對象,都由一個單獨的類來創建;在游戲中主要有三類游戲對象:一是坦克,二是子彈,三是道具;其中,坦克又分為玩家坦克和電腦坦克,子彈又分為玩家子彈和電腦子彈。於是,我們可以對坦克進行抽象形成一個抽象父類:TankFather,然后分別創建兩個子類:PlayerTank和EnemyTank;然后對子彈進行抽象形成一個抽象類:BulletFather,然后分別創建兩個子類:PlayerBullet和EnemyBullet。但是,我們發現這些游戲對象都有一些共同的屬性和方法,例如X,Y軸坐標,長度和寬度,以及繪制(Draw())和移動(Move())的方法,這時我們可以設計一個抽象類,形成了GameObject類:將共有的東西封裝起來,減少開發時的冗余代碼,提高程序的可擴展性,符合面向對象設計的思路:

  (2)計划生育好

  在整個游戲中,我們的玩家坦克對象只有一個,也就是說在內存中只需要存一份即可。這時,我們想到了偉大的計划生育政策,於是我們想到了使用單例模式。借助單例模式,可以保證只生成一個玩家坦克的實例,即為程序提供一個全局訪問點,避免重復創建浪費不必要的內存。當然,除了玩家坦克外,我們的電腦坦克集合、子彈集合等集合對象實例也保證只有一份存儲,降低游戲開銷;

  (3)對象的運動

  在整個游戲過程中,玩家可以通過鍵盤上下左右鍵控制玩家坦克的上下左右運動,而坦克的運動本質上還是改變游戲對象的X軸和Y軸的坐標,然后一直不間斷地在窗體上重繪游戲對象。相比玩家坦克的移動,電腦坦克的移動則完全是通過程序中設置的隨機函數控制上下左右實現的,而坦克們發出的子彈執行的運動則是從上到下或從下到上,從左到右或從右到左。

position

  (4)設計流程圖

三、關鍵代碼實現

3.1 設計抽象父類封裝共有屬性

  1     /// <summary>
  2     /// 所有游戲對象的基類
  3    /// </summary>
  4     public abstract class GameObject
  5     {
  6         #region 游戲對象的屬性
  7         public int X
  8         {
  9             get;
 10             set;
 11         }
 12 
 13         public int Y
 14         {
 15             get;
 16             set;
 17         }
 18 
 19         public int Width
 20         {
 21             get;
 22             set;
 23         }
 24 
 25         public int Height
 26         {
 27             get;
 28             set;
 29         }
 30 
 31         public int Speed
 32         {
 33             get;
 34             set;
 35         }
 36 
 37         public int Life
 38         {
 39             get;
 40             set;
 41         }
 42 
 43 
 44         public Direction Dir
 45         {
 46             get;
 47             set;
 48         }
 49         #endregion
 50 
 51         #region 初始化游戲對象
 52         public GameObject(int x, int y, int width, int height,
 53                 int speed, int life, Direction dir)
 54         {
 55             this.X = x;
 56             this.Y = y;
 57             this.Width = width;
 58             this.Height = height;
 59             this.Speed = speed;
 60             this.Life = life;
 61             this.Dir = dir;
 62         }
 63 
 64         public GameObject(int x, int y)
 65             : this(x, y, 0, 0, 0, 0, 0)
 66         {
 67 
 68         }
 69 
 70         public GameObject(int x, int y, int width, int height)
 71             : this(x, y, width, height, 0, 0, 0)
 72         {
 73 
 74         }
 75         #endregion
 76 
 77         #region 游戲對象公有方法
 78         /// <summary>
 79         /// 抽象方法:繪制自身
 80         /// </summary>
 81         /// <param name="g"></param>
 82         public abstract void Draw(Graphics g);
 83 
 84         /// <summary>
 85         /// 虛方法:移動自身
 86         /// </summary>
 87         public virtual void Move()
 88         {
 89             switch (this.Dir)
 90             {
 91                 case Direction.Up:
 92                     this.Y -= this.Speed;
 93                     break;
 94                 case Direction.Down:
 95                     this.Y += this.Speed;
 96                     break;
 97                 case Direction.Left:
 98                     this.X -= this.Speed;
 99                     break;
100                 case Direction.Right:
101                     this.X += this.Speed;
102                     break;
103             }
104             // 在游戲對象移動完成后判斷一下:當前游戲對象是否超出當前的窗體 
105             if (this.X <= 0)
106             {
107                 this.X = 0;
108             }
109             if (this.Y <= 0)
110             {
111                 this.Y = 0;
112             }
113             if (this.X >= 720)
114             {
115                 this.X = 720;
116             }
117             if (this.Y >= 580)
118             {
119                 this.Y = 580;
120             }
121         }
122 
123         /// <summary>
124         /// 獲取所在區域,用於碰撞檢測
125       /// </summary>
126         /// <returns>矩形區域</returns>
127         public Rectangle GetRectangle()
128         {
129             return new Rectangle(this.X, this.Y, this.Width, this.Height);
130         }
131         #endregion
132     }
View Code

  一切皆對象,這里封裝了游戲對象坦克和子彈以及其他游戲對象共有的屬性,以及兩個抽象方法,讓對象們(坦克?子彈?爆炸效果?出現效果?)自己去實現。

3.2 設計單例模式減少對象創建

  1     /// <summary>
  2     /// 單例游戲對象類
  3    /// </summary>
  4     public class SingleObject
  5     {
  6         private SingleObject()
  7         { }
  8 
  9         private static SingleObject _singleObject = null;
 10 
 11         public static SingleObject GetInstance()
 12         {
 13             if (_singleObject == null)
 14             {
 15                 _singleObject = new SingleObject();
 16             }
 17             return _singleObject;
 18         }
 19 
 20         /// <summary>
 21         /// 玩家坦克單一實例
 22         /// </summary>
 23         public PlayerTank Player
 24         {
 25             get;
 26             set;
 27         }
 28         /// <summary>
 29         /// 電腦坦克集合單一實例
 30         /// </summary>
 31         public List<EnemyTank> EnemyList
 32         {
 33             get;
 34             set;
 35         }
 36         /// <summary>
 37         /// 玩家坦克子彈對象集合單一實例
 38         /// </summary>
 39         public List<PlayerBullet> PlayerBulletList
 40         {
 41             get;
 42             set;
 43         }
 44         /// <summary>
 45         /// 電腦坦克子彈對象集合單一實例
 46         /// </summary>
 47         public List<EnemyBullet> EnemyBulletList
 48         {
 49             get;
 50             set;
 51         }
 52         /// <summary>
 53         /// 爆炸效果對象集合單一實例
 54         /// </summary>
 55         public List<Boom> BoomImageList
 56         {
 57             get;
 58             set;
 59         }
 60         /// <summary>
 61         /// 閃爍圖片效果集合單一實例
 62         /// </summary>
 63         public List<TankBorn> TankBornList
 64         {
 65             get;
 66             set;
 67         }
 68         /// <summary>
 69         /// 游戲道具集合單一實例
 70         /// </summary>
 71         public List<Prop> PropList
 72         {
 73             get;
 74             set;
 75         }
 76 
 77         /// <summary>
 78         /// 新增游戲對象
 79         /// </summary>
 80         /// <param name="go">游戲對象</param>
 81         public void AddGameObject(GameObject go)
 82         {
 83             if (go is PlayerTank)
 84             {
 85                 this.Player = go as PlayerTank;
 86             }
 87             else if (go is EnemyTank)
 88             {
 89                 if (this.EnemyList == null)
 90                 {
 91                     this.EnemyList = new List<EnemyTank>();
 92                 }
 93                 this.EnemyList.Add(go as EnemyTank);
 94             }
 95             else if (go is PlayerBullet)
 96             {
 97                 if (this.PlayerBulletList == null)
 98                 {
 99                     this.PlayerBulletList = new List<PlayerBullet>();
100                 }
101                 this.PlayerBulletList.Add(go as PlayerBullet);
102             }
103             else if (go is EnemyBullet)
104             {
105                 if (this.EnemyBulletList == null)
106                 {
107                     this.EnemyBulletList = new List<EnemyBullet>();
108                 }
109                 this.EnemyBulletList.Add(go as EnemyBullet);
110             }
111             else if (go is Boom)
112             {
113                 if (this.BoomImageList == null)
114                 {
115                     this.BoomImageList = new List<Boom>();
116                 }
117                 this.BoomImageList.Add(go as Boom);
118             }
119             else if (go is TankBorn)
120             {
121                 if (this.TankBornList == null)
122                 {
123                     this.TankBornList = new List<TankBorn>();
124                 }
125                 this.TankBornList.Add(go as TankBorn);
126             }
127             else if (go is Prop)
128             {
129                 if (this.PropList == null)
130                 {
131                     this.PropList = new List<Prop>();
132                 }
133                 this.PropList.Add(go as Prop);
134             }
135             else
136             {
137                 return;
138             }
139         }
140 
141         /// <summary>
142         /// 移除游戲對象
143         /// </summary>
144         /// <param name="go"></param>
145         public void RemoveGameObject(GameObject go)
146         {
147             if (go is PlayerTank)
148             {
149                 // 玩家被擊中后
150             }
151             else if (go is PlayerBullet)
152             {
153                 PlayerBulletList.Remove(go as PlayerBullet);
154             }
155             else if (go is EnemyBullet)
156             {
157                 EnemyBulletList.Remove(go as EnemyBullet);
158             }
159             else if (go is EnemyTank)
160             {
161                 EnemyList.Remove(go as EnemyTank);
162             }

163             else if (go is Boom)
164             {
165                 BoomImageList.Remove(go as Boom);
166             }
167             else if (go is TankBorn)
168             {
169                 TankBornList.Remove(go as TankBorn);
170             }
171             else if (go is Prop)
172             {
173                 PropList.Remove(go as Prop);
174             }
175             else
176             {
177                 return;
178             }
179         }
180 
181         /// <summary>
182         /// 繪制游戲對象
183         /// </summary>
184         /// <param name="g">繪圖圖面</param>
185         public void Draw(Graphics g)
186         {
187             // Step1:繪制玩家坦克
188          if(Player != null)
189             {
190                 Player.Draw(g);
191             }
192             // Step2:繪制電腦坦克
193          if(EnemyList != null)
194             {
195                 for (int i = 0; i < EnemyList.Count; i++)
196                 {
197                     EnemyList[i].Draw(g);
198                 }
199             }
200             // Step3:繪制子彈效果
201          if (PlayerBulletList != null)
202             {
203                 for (int i = 0; i < PlayerBulletList.Count; i++)
204                 {
205                     PlayerBulletList[i].Draw(g);
206                 }
207             }
208             if (EnemyBulletList != null)
209             {
210                 for (int i = 0; i < EnemyBulletList.Count; i++)
211                 {
212                     EnemyBulletList[i].Draw(g);
213                 }
214             }
215             // Step4:繪制爆炸效果
216          if (BoomImageList != null)
217             {
218                 for (int i = 0; i < BoomImageList.Count; i++)
219                 {
220                     BoomImageList[i].Draw(g);
221                 }
222             }
223             // Step5:繪制閃爍效果
224          if (TankBornList != null)
225             {
226                 for (int i = 0; i < TankBornList.Count; i++)
227                 {
228                     TankBornList[i].Draw(g);
229                 }
230             }
231             // Step6:繪制游戲道具
232          if (PropList != null)
233             {
234                 for (int i = 0; i < PropList.Count; i++)
235                 {
236                     PropList[i].Draw(g);
237                 }
238             }
239         }
240     }
View Code

  這里借助單例模式,保證玩家坦克只有一個存儲,電腦坦克集合也只有一個,而具體的電腦坦克對象則分別在集合中Add和Remove。

3.3 設計道具檢測方法使玩家能夠碉堡

  (1)設計游戲道具類,為三種類型的道具設置一個標志屬性:

 1     /// <summary>
 2     /// 游戲道具類
 3    /// </summary>
 4     public class Prop : GameObject
 5     {
 6         private static Image imgStar = Resources.star;
 7         private static Image imgBomb = Resources.bomb;
 8         private static Image imgTimer = Resources.timer;
 9 
10         /// <summary>
11         /// 游戲道具類型:0-五角星,1-炸彈,2-定時器
12       /// </summary>
13         public int PropType
14         {
15             get;
16             set;
17         }
18 
19         public Prop(int x, int y, int propType)
20             : base(x, y, imgStar.Width, imgStar.Height)
21         {
22             this.PropType = propType;
23         }
24 
25         public override void Draw(System.Drawing.Graphics g)
26         {
27             switch(PropType)
28             {
29                 case 0:
30                     g.DrawImage(imgStar,this.X,this.Y);
31                     break;
32                 case 1:
33                     g.DrawImage(imgBomb, this.X, this.Y);
34                     break;
35                 case 2:
36                     g.DrawImage(imgTimer, this.X, this.Y);
37                     break;
38             }
39         }
40     }
View Code

  (2)在單例類中創建一個判斷道具類型的方法,根據標志屬性區分不同道具,並進行對應的道具效果:

 1         /// <summary>
 2         /// 判斷游戲道具類型
 3       /// </summary>
 4         /// <param name="propType"></param>
 5         public void JudgePropType(int propType)
 6         {
 7             switch (propType)
 8             {
 9                 case 0:// 吃到五角星讓玩家子彈速度變快
10                     if (Player.BulletLevel < 2)
11                     {
12                         Player.BulletLevel++;
13                     }
14                     break;
15                 case 1:// 吃到炸彈讓一定區域內的電腦坦克爆炸
16                     for (int i = 0; i < EnemyList.Count; i++)
17                     {
18                         // 把電腦坦克生命值設置為0
19                         EnemyList[i].Life = 0;
20                         EnemyList[i].IsOver();
21                     }
22                     break;
23                 case 2:// 吃到定時器讓所有坦克定住一段時間
24                     for (int i = 0; i < EnemyList.Count; i++)
25                     {
26                         EnemyList[i].isPause = true;
27                     }
28                     break;
29             }
30         }
View Code

3.4 設計碰撞檢測方法使電腦坦克可以減少

  (1)Rectangle的IntersectsWith方法

  在游戲界面中,任何一個游戲對象我們都可以視為一個矩形區域(Rectangle類實例),它的坐標是X軸和Y軸,它還有長度和寬度,可以輕松地確定一個它所在的矩形區域。那么,我們可以通過Rectangle的IntersectsWith方法確定兩個Rectangle是否存在重疊,如果有重疊,此方法將返回 true;否則將返回 false。那么,在坦克大戰中主要是判斷兩種情況:一是玩家或電腦坦克發射的子彈是否擊中了對方?二是玩家是否吃到了游戲道具?

  (2)在定時器事件中定期執行碰撞檢測方法

  1         /// <summary>
  2         /// 碰撞檢測
  3       /// </summary>
  4         public void CollisionDetection()
  5         {
  6             #region Step1:判斷玩家發射的子彈是否擊中了電腦坦克
  7             // Step1:判斷玩家發射的子彈是否擊中了電腦坦克
  8          if (PlayerBulletList != null)
  9             {
 10                 for (int i = 0; i < PlayerBulletList.Count; i++)
 11                 {
 12                     for (int j = 0; j < EnemyList.Count; j++)
 13                     {
 14                         if (PlayerBulletList[i].GetRectangle().IntersectsWith(EnemyList[j].GetRectangle()))
 15                         {
 16                             // 電腦坦克減少生命值
 17                             EnemyList[j].Life -= PlayerBulletList[i].Power;
 18                             EnemyList[j].IsOver();
 19                             // 移除子彈對象實例
 20                             PlayerBulletList.Remove(PlayerBulletList[i]);
 21                             break;
 22                         }
 23                     }
 24                 }
 25             }
 26             #endregion
 27 
 28             #region Step2:判斷電腦發射的子彈是否擊中了玩家坦克
 29             // Step2:判斷電腦發射的子彈是否擊中了玩家坦克
 30          if (EnemyBulletList != null)
 31             {
 32                 for (int i = 0; i < EnemyBulletList.Count; i++)
 33                 {
 34                     if (EnemyBulletList[i].GetRectangle().IntersectsWith(Player.GetRectangle()))
 35                     {
 36                         // 玩家坦克減少生命值
 37                         Player.Life -= EnemyBulletList[i].Power;
 38                         Player.IsOver();
 39                         // 移除子彈對象實例
 40                         EnemyBulletList.Remove(EnemyBulletList[i]);
 41                     }
 42                 }
 43             }
 44             #endregion
 45 
 46             #region Step3:判斷玩家是否吃到了游戲道具
 47             // Step3:判斷玩家是否吃到了游戲道具
 48          if (PropList != null)
 49             {
 50                 for (int i = 0; i < PropList.Count; i++)
 51                 {
 52                     if (PropList[i].GetRectangle().IntersectsWith(Player.GetRectangle()))
 53                     {
 54                         // 播放吃到道具音效
 55                         SoundPlayer sp = new SoundPlayer(Resources.add);
 56                         sp.Play();
 57                         // 增加子彈等級
 58                         JudgePropType(PropList[i].PropType);
 59                         // 移除游戲道具實例
 60                         PropList.Remove(PropList[i]);
 61                     }
 62                 }
 63             }
 64             #endregion
 65 
 66             #region Step4:判斷電腦坦克是否和玩家坦克相撞
 67          if (EnemyList != null)
 68             {
 69                 for (int i = 0; i < EnemyList.Count; i++)
 70                 {
 71                     if (EnemyList[i].GetRectangle().IntersectsWith(Player.GetRectangle()))
 72                     {
 73                         switch (Player.Dir)
 74                         {
 75                             case Direction.Up:
 76                                 EnemyList[i].Dir = Direction.Right;
 77                                 break;
 78                             case Direction.Down:
 79                                 EnemyList[i].Dir = Direction.Left;
 80                                 break;
 81                             case Direction.Left:
 82                                 EnemyList[i].Dir = Direction.Up;
 83                                 break;
 84                             case Direction.Right:
 85                                 EnemyList[i].Dir = Direction.Down;
 86                                 break;
 87                         }
 88                     }
 89                 }
 90             } 
 91             #endregion
 92 
 93             #region Step5:判斷電腦坦克A是否和電腦坦克B發生了碰撞
 94             // Step5:判斷電腦坦克A是否和電腦坦克B發生了碰撞
 95          if (EnemyList != null)
 96             {
 97                 for (int i = 0; i < EnemyList.Count - 1; i++)
 98                 {
 99                     for (int j = i + 1; j < EnemyList.Count; j++)
100                     {
101                         if (EnemyList[i].GetRectangle().IntersectsWith(EnemyList[j].GetRectangle()))
102                         {
103                             switch (EnemyList[i].Dir)
104                             {
105                                 case Direction.Up:
106                                     EnemyList[j].Dir = Direction.Right;
107                                     break;
108                                 case Direction.Down:
109                                     EnemyList[j].Dir = Direction.Left;
110                                     break;
111                                 case Direction.Left:
112                                     EnemyList[j].Dir = Direction.Up;
113                                     break;
114                                 case Direction.Right:
115                                     EnemyList[j].Dir = Direction.Down;
116                                     break;
117                             }
118                         }
119                     }
120                 }
121             } 
122             #endregion
123         }
View Code

四、個人開發小結

  從下面的運行效果可以看出,此次DEMO主要完成了幾個比較核心的內容:一是玩家坦克和電腦坦克的移動,二是玩家和電腦發射子彈,三是坦克和子彈的碰撞檢測。

  當然,還有很多核心的內容沒有實現,比如:計算被擊中的電腦坦克數量、游戲歡迎界面和結束界面等。希望有興趣的童鞋可以去繼續完善實現,這里提供一個我的坦克大戰實現僅供參考,謝謝!

參考資料

  趙建宇,《六小時C#開發搞定坦克大戰游戲》:http://bbs.itcast.cn/thread-28540-1-1.html

附件下載

  MyTankGame:http://pan.baidu.com/s/1o6wUGae

 


免責聲明!

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



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