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轴的坐标,然后一直不间断地在窗体上重绘游戏对象。相比玩家坦克的移动,电脑坦克的移动则完全是通过程序中设置的随机函数控制上下左右实现的,而坦克们发出的子弹执行的运动则是从上到下或从下到上,从左到右或从右到左。
(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 }