首页 > 代码库 > 自己动手写游戏:坦克撕逼大战

自己动手写游戏:坦克撕逼大战

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         #endregion132     }
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             else136             {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             else176             {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 PropType14         {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                         // 把电脑坦克生命值设置为019                         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             #endregion123         }
View Code

四、个人开发小结

  从下面的运行效果可以看出,此次DEMO主要完成了几个比较核心的内容:一是玩家坦克和电脑坦克的移动,二是玩家和电脑发射子弹,三是坦克和子弹的碰撞检测。

  当然,还有很多核心的内容没有实现,比如:计算被击中的电脑坦克数量、游戏欢迎界面和结束界面等。希望有兴趣的童鞋可以去继续完善实现,这里提供一个我的坦克大战实现仅供参考,谢谢!

参考资料

  赵建宇,《六小时C#开发搞定坦克大战游戏》:http://bbs.itcast.cn/thread-28540-1-1.html

附件下载

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

 

作者:周旭龙

出处:http://www.cnblogs.com/edisonchou/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

自己动手写游戏:坦克撕逼大战