【文章标题】用面向对象的思想探讨游戏“魔兽争霸”(1)
【文章作者】曾健生
【作者邮箱】zengjiansheng1@126.com
【作者QQ】190678908
【作者博客】http://blog.youkuaiyun.com/newjueqi
【编程环境】JDK 1.6.0_01
【作者声明】欢迎转载文章,但转载请保留文章的完整性以及注明文章的出处。
*******************************************************************************
前言:面向对象思想是学习java, .net等计算机语言的核心思想,在本人学习的过程中,本人直到最近对面向对象思想有了一个大突破后才发现原来在玩 “魔兽争霸”这个游戏的过程中就已经包含了深刻的面向对象的思想,现在根据自己对面向对象思想的理解结合“魔兽争霸”这个游戏进行相关的阐述(本人知道很多兄弟们对“魔兽争霸”等相关即时战略游戏相当沉迷,用“魔兽争霸”举例说明能减少学习的枯燥性,看完本文后就能发现其实面向对象的思想我们早已熟悉了,只是没发现而已),希望能对各位理解面向对象的思想有所帮助。阅读本文前最好对即时战略游戏有所理解,即时战略游戏指魔兽争霸,星际(当然也包括帝国时代,红色警戒等游戏),有所理解是指起码也要知道游戏是怎么玩的。最后,请原谅由于本人对“魔兽争霸”不太熟悉而在魔兽的专业术语的使用上有所错误,其实本人只是“魔兽争霸”菜鸟(勉强能打赢初级电脑),而且本人也有半年多没玩,很多内容都忘记,阅读本文的“魔兽”玩家请原谅我吧^-^
本人才疏学浅,对于文中出现的各种错误,敬请各位大侠指出!!!
这里也简单阐述一下面向对象的概念(详细的定义读者可参考相关的书籍或文章):
面向对象的思想力图使计算机中描述的事物和现实生活中的尽量相近,其中类是指具有相同属性,相同行为的一类事物;实例是指该类事物的具体例子
首先开始就文中的一些概念进行约定以减少阅读的困难:
(1) 我们玩“魔兽争霸”游戏是在一个地图(是指2D地图)中进行,如果现在有一个人族的农民站在地图上,那么这个农民在地图上有个坐标(坐标的原点是地图左下角),譬如是农民的坐标(200,100),那么在地图上的标识如下(图1),这个坐标概念在下文中经常用到,各位读者必须要理解是怎么一回事
(2) 全文中都是假设玩家选的种族是精灵族
其实我们在玩魔兽的过程中对类的概念已经耳熟能详了(对于这一点,是本人在前几天对面向对象有了一个更深刻的认识后才发现,各位玩家看了后一定会恍然大悟^-^)
下面就面向对象中的属性,行为,类,实例等概念用游戏的例子说明一下
(1) 属性:是说明某个物体的基本特征,通过一系列的属性数据,我们就能得出结论某个物体是什么?譬如一个精灵族的小精灵,它的生命是120,防御是0,看起来像是发红光的物体,知道了小精灵的以上数据,就对小精灵有所了解。如图1
图1
(2) 行为:说明某个物体能干什么?譬如小精灵能采矿(),能建造生命之树(),能移动()等,这都是小精灵的能干的事情。如图2
图2
(3) 类:是某一种事物的描述。我们知道在精灵族种小精灵的生命值120,防御力是0,小精灵能采矿(),能建造生命之树(),能移动(),但我们不需要知道具体到底指的是哪个小精灵,反正小精灵这个概率就有以上的内容。所以小精灵就是一个类,它可以存在于抽象层次(知道是什么一回事),但不需要实体。在精灵古树种有个生产小精灵的按钮,我们知道这个按钮能生产小精灵,生产的小精灵除了位置和内部ID号外其他都是一样的。所以这个生产小精灵的按钮就是已经蕴含了面向对象中的“小精灵”类的概念。如图3
图3
(4) 实例:类只是某一种事物的抽象的概念。就正如“小精灵”类一样,如果不训练一个小精灵去采矿,那么小精灵类永远不会采矿。如果我们需要一个小精灵去采矿,我们就必需点击一下训练小精灵的按钮训练一个小精灵出来点击训练小精灵按钮创造一个小精灵,用面向对象的术语描述就是用小精灵类产生了一个小精灵的实例。训练出来的小精灵就是小精灵类一个实例。图4就是用小精灵类创造的多个实例:
图4
经过以上的论述,本人得出了一个结论:每个玩家都已很熟悉面向对象思想。玩魔兽的过程就像是一个面向对象编程的过程:我们玩游戏就是用暴雪公司提供的各种类和方法,思考用那些类(英雄类,小精灵类,战士类等),创造多少个实例(训练多少各精灵,生产多少个弓箭手,需要用哪个英雄),调用哪些方法(小精灵去采矿(),小精灵建造生命之树(),弓箭手攻击())来获取胜利,在这个基础上玩家们创造出不同的战术获取胜利。
所以正在学习面向对象的各位玩家,其实我们早已对面向对象的那套思想非常熟悉,只是我们不知道这就是面向对象思想而已。希望阅读完本文的各位玩家能根据本文的内容及时把思维转换过来,更快地迈入面向对象的世界。
现在我们开始在程序的角度用“魔兽争霸”游戏阐述面向对象的思想(以下的代码进行了最大程度的简化,只阐述最核心的部分,各位读者要明白,真正的游戏编程没有这么简单^-^,Let’ Go!!!
那么现在我们从程序员的角度思考一下,如果现在有这么一个场景:玩家生产了一个弓箭手去攻击对手不死族的食尸鬼。如果你是一个程序员,应该怎么用面向对象编程完成这个场景动作?
根据我们玩“魔兽争霸”的经验,第一步是生产一个弓箭手,这个不难,但如果玩家是生产2个,3个,4个……,如果每次生产一个弓箭手都要把弓箭手的所有代码重写一遍,那不是把我们写程序的活活累死!!!
不要着急,我们可以分析一下每个弓箭手有什么不同。现在我们再回顾一下类的概念:类是指具有相同属性,相同行为的一类事物。对于弓箭手,就正如我们玩家所熟悉的,每个弓箭手相对于其他弓箭手唯一的不同就是所在的位置(也就是在地图上的坐标)还有一个ID,其它的什么攻击力,防御力,生命值,移动的方法:步行,攻击的方法:用弓箭射击 ,受到攻击的防御行为等都是一样的。那么对于所有的弓箭手来说,除了所在的位置不同外的所有属性(攻击力,防御力,生命值),所有行为(移动的方法:步行,攻击的方法:弓箭射击,受到攻击的行为)是不是一样啊!这不是已经符合了类的定义吗?所以把弓箭手抽象为一个类Bower。
类的定义如下:
//这是一个弓箭手类
class Bower
{
private int posX; //弓箭手在地图上X的坐标
private int posY; //弓箭手在地图上Y的坐标
private int ID; //弓箭手在地图上Y的坐标
private int lifeNum; //弓箭手的生命值
private int attackNum; //弓箭手的攻击力
private int untenNum; //弓箭手的防御力
//构造函数,生产一个弓箭手,传入参数为在地图中的坐标
public Bower( int IDNum, int x, int y )
{
posX=x; //弓箭手生产出来后在地图上X的坐标
posY=y; //弓箭手生产出来后在地图上Y的坐标
lifeNum= 245; //弓箭手的默认生命值
attackNum=16; //弓箭手的默认攻击力
untenNum=0; //弓箭手的默认防御力
ID=IDNum; //弓箭手的ID
System.out.println("弓箭手 "+ID+"生产完毕了");
}
//默认构造函数
public Bower( )
{
}
/******
一般来说,生命值,攻击力,防御力等都属于对象的核心数据,对它们
的访问必须要严格控制,所以设计出getLifeNum(),getAttackNum(),
getUntenNum()这三个方法
*/
//获取弓箭手的剩余生命值
public int getLifeNum()
{
return lifeNum;
}
//获取弓箭手的攻击力
public int getAttackNum()
{
return attackNum;
}
//获取弓箭手的防御力
public int getUntenNum()
{
return untenNum;
}
//获取弓箭手的ID号
public int getID()
{
return ID;
}
//获取弓箭手的X坐标
public int getX()
{
return posX;
}
//获取弓箭手的Y坐标
public int getY()
{
return posY;
}
//弓箭手移动的行为,就是一般情况下用点击了
//一个弓箭手后命令弓箭手移动到某个位置所用的方法
//传入参数为要移动到的对象
public void moveTo( Ghost gs )
{
posX=gs.getX();
posY=gs.getY();
System.out.println( "弓箭手"+ID+"移动到地点 "+posX+","+posY );
}
//用弓箭手攻击食尸鬼的方法,传入的参数为攻击的对象
//附:本人感觉这个攻击行为抽象的设计非常差,如果有好的方法,
//敬请指教
public void attackByBow( Ghost gs )
{
gs.getHunt( this );
}
//受到攻击时调用这个方法计算伤害值
public void getHunt( Ghost gs )
{
//只有在食尸鬼和弓箭手都没死亡的前提下会攻击
if( gs.getLifeNum()>0 && getLifeNum()>0 )
{
//如果受到的攻击值大于自身的生命值表示对象死亡
if( (gs.getAttackNum()-getUntenNum())>=lifeNum )
{
lifeNum=0;
System.out.print("弓箭手"+getID()+"受到食尸鬼"+gs.getID());
System.out.print("的攻击力"+gs.getAttackNum());
System.out.println(",弓箭手"+ID+"死亡");
}
else //用生命值减去受到的伤害值
{
lifeNum=lifeNum-(gs.getAttackNum()-getUntenNum());
System.out.print("弓箭手"+getID()+"受到食尸鬼"+gs.getID());
System.out.print("的攻击力"+gs.getAttackNum());
System.out.println(",剩余生命值为"+getLifeNum());
}
}
}
}
对于食尸鬼,可以用同样的抽象方法成一个食尸鬼类
//这是一个食尸鬼类
class Ghost
{
private int posX; //食尸鬼在地图上X的坐标
private int posY; //食尸鬼在地图上Y的坐标
private int ID; //食尸鬼在地图上Y的坐标
private int lifeNum; //食尸鬼的生命值
private int attackNum; //食尸鬼的攻击力
private int untenNum; //食尸鬼的防御力
//构造函数,生产一个食尸鬼,传入参数为在地图中的坐标
public Ghost( int IDNum,int x, int y )
{
posX=x; //食尸鬼生产出来后在地图上X的坐标
posY=y; //食尸鬼生产出来后在地图上Y的坐标
lifeNum= 245; //食尸鬼的默认生命值
attackNum=12; //食尸鬼的默认攻击力
untenNum=0; //食尸鬼的默认防御力
ID=IDNum; //食尸鬼的ID
System.out.println("食尸鬼 "+ID+"生产完毕了");
}
//默认构造函数
public Ghost( )
{
}
//获取食尸鬼的剩余生命值
public int getLifeNum()
{
return lifeNum;
}
//获取食尸鬼的攻击力
public int getAttackNum()
{
return attackNum;
}
//获取食尸鬼的防御力
public int getUntenNum()
{
return untenNum;
}
//获取食尸鬼的ID号
public int getID()
{
return ID;
}
//获取食尸鬼的X坐标
public int getX()
{
return posX;
}
//获取食尸鬼的Y坐标
public int getY()
{
return posY;
}
//食尸鬼移动的行为,就是一般情况下用点击了
//一个食尸鬼后命令食尸鬼移动到某个位置所用的方法
//传入参数为要移动到的对象
public void moveTo( Bower bo )
{
posX=bo.getX();
posY=bo.getY();
System.out.println( "食尸鬼"+ID+"移动到地点 "+posX+","+posY );
}
//用食尸鬼攻击弓箭手的方法,传入的参数为攻击的对象
//附:本人感觉这个攻击行为抽象的设计非常差,如果有好的方法,
//敬请指教
public void attackByWater( Bower bo )
{
bo.getHunt( this );
}
//受到攻击时调用这个方法计算伤害值
public void getHunt( Bower bo )
{
//只有在食尸鬼和弓箭手都没死亡的前提下会攻击
if( bo.getLifeNum()>0 && getLifeNum()>0 )
{
//如果受到的攻击值大于自身的生命值表示对象死亡
if( (bo.getAttackNum()-getUntenNum())>=lifeNum )
{
lifeNum=0;
System.out.print("食尸鬼"+getID()+"受到弓箭手"+bo.getID());
System.out.print("的攻击力"+bo.getAttackNum());
System.out.println(",食尸鬼"+ID+"死亡");
}
else //用生命值减去受到的伤害值
{
lifeNum=lifeNum-
(bo.getAttackNum()-getUntenNum());
System.out.print("食尸鬼"+getID()+"受到弓箭手"+bo.getID());
System.out.print("的攻击力"+bo.getAttackNum());
System.out.println(",剩余生命值为"+getLifeNum() );
}
}
}
}
那么现在我们根据上面写的类,用一段代码模拟玩家生产了一个弓箭手去攻击对手不死族的食尸鬼这个过程
程序运行后结果如下图所示
class GameStart
{
public static void main( String args[] )
{
//下面一段代码产出了一个食尸鬼
Ghost gs= new Ghost( 1, 90, 90 );
//下面这两行代码表示玩家创建了两个弓箭手,相当于玩家按了两下生产弓箭手的按钮
Bower bo1= new Bower( 1, 180, 180 );
Bower bo2= new Bower( 2, 180, 180 );
//这里的设计就能体现面向对象的好处:
//1.直观,把弓箭手移动到食尸鬼的位置代码为bo1.moveTo( gs ),
// 从字面上就能理解这句代码的含义
//2.把程序员从繁琐的移动细节中解放出来,我们只需要调用弓箭手的
// 方法命令弓箭手移动,至于弓箭手移动时走路的方式如何,是走直线还是
// 曲线,这些都不需要在这里写代码,因为这些内容都是封装在类的内部处理
//下面这两行代码表示玩家指挥两个弓箭手移动到要攻击的食尸鬼的位置,
//相当于玩家指挥两个弓箭手走向食尸鬼
bo1.moveTo( gs );
bo2.moveTo( gs );
//这里也同样体现了面向对象编程的好处,我们只需要给弓箭手发出
//攻击的命令就行,至于攻击的细节(用手还是用脚,攻击的力度等)都是
//封装在类的内部实现,程序员调用这个方法时不需要理会里面的细节
//下面的while循环是两个弓箭手开始攻击食尸鬼攻击的过程
//为了编写代码的方便性,假设弓箭手和食尸鬼相遇时才互相攻击,
//没有远程攻击手段,只站在原地互相攻击,请各位魔兽玩家原谅我吧^-^
//攻击过程抽象如下(有点像回合制的RPG游戏^-^)
//1.弓箭手1攻击食尸鬼后食尸鬼反击弓箭手1
//2.弓箭手2攻击食尸鬼后食尸鬼反击弓箭手2
//3.如果食尸鬼或两个弓箭手都没死亡就重复1,2的过程
while( true )
{
//弓箭手1攻击食尸鬼
bo1.attackByBow( gs );
//如果食尸鬼没死亡就反击
gs.attackByWater( bo1 );
//弓箭手2攻击食尸鬼
bo2.attackByBow( gs );
//如果食尸鬼没死亡就反击
gs.attackByWater( bo2 );
//攻击结束的条件是以下两个条件之一(当生命值等于0时表示死亡)
//1.食尸鬼死亡
//2.两个弓箭手死亡
if( (gs.getLifeNum()==0) || (bo1.getLifeNum()==0 && bo2.getLifeNum()==0) )
{
break;
}
}
//根据食尸鬼的生命值是否等于0判断任何
if( gs.getLifeNum()>0 )
{
System.out.println("两个弓箭手死亡,任务失败");
}
else
{
System.out.println("食尸鬼死亡,任务成功");
}
}
}
图5
后记:
面向对象思想是贴近于生活的,但生活中的内容实在是太多太复杂,不容易从中学习面向对象的思想,本人偶然的机会下发现“魔兽争霸”这个包含了丰富面向对象思想的游戏(严格意义上生活中的每个东西或多或少都包含了面向对象的思想),而且面向对象的元素特别清晰,正如在本文中论述的一样,行为,熟悉,类,实例等面向对象的元素在游戏中都有清晰的表示(很多都是在游戏画面显示出来的,告诉你哪些是对象的属性,哪些是对象的方法),而且很多同学对这个游戏有相当的感性认识,用这个游戏讲解面向对象,能增加亲切感,减少学习的难度。
声明一下,本人不是鼓励各位只要玩游戏就能学好编程,初学编程最重要的是动手实践写程序,阅读本文的读者请切记,切记,切记!!!
最后说一下,本文中所写的代码可以改进的内容实在太多!!!我们知道面向对象有三大特性:封装,继承,多态,本文的代码只是用到了封装这个特性,而继承和多态都没有涉及到。我们仔细审视一遍所有的代码可发现,弓箭手类Bower和食尸鬼类Ghost还是有相当多的相同之处,但代码中却把他们相同的内容重复写了一遍,其实我们可以用面向对象中的继承的思想,把弓箭手类Bower和食尸鬼类Ghost相同的属性和行为都抽象为一个战士基类,使弓箭手类Bower和食尸鬼类Ghos都继承于这个战士基类; 另外在弓箭手类Bower和食尸鬼类Ghost的攻击方法中,其实无论是攻击谁,攻击的手段都是一样的,没必要为不同的攻击对象单独写一个方法,这时就能用到多态的思想改进代码。
上面论述的改进内容,将在下篇文章《用面向对象的思想探讨游戏“魔兽争霸”(2)》中实现。
本人才疏学浅,对于文中出现的各种错误,敬请各位大侠指出!!!个人的联系方式:zengjiansheng1@126.com 或者在博客上留言
最后附上本文的思维导图:
(图片博客上不能显示完整,请保存到本地后再查看)