做个RPG游戏主人翁 第六篇 英雄的宿命——战斗系统
软件世界
阿志从客栈出来后正打算到妖怪的老巢去,这时精灵阿比叫住了阿志:“等等!以你现在的能力只会去找死。”
阿志:“那我要怎么做?”
精灵阿比:“拿着这武功秘籍,你只有学会了里面的招式才会有胜算。”
阿志接过秘籍,开始认真地研究起来。
本期是RPG游戏开发的最后一期,大家将在这期学习到游戏中精彩的打斗是如何实现的,以便完成RPG游戏开发中最后一个重要的组成部分──战斗系统。
一、降魔大法──招式表
没有一招半式就想拿着把剑纵横四海吗?相信你会连打一些小喽啰都会很吃力。在大多数武侠RPG游戏中,主角在他的冒险旅程中都要学武功。不论这些武功是从哪里学来的,角色都会把他所学到的武功记录在他的招式表中。在战斗中角色所使出的那些华丽的招式都是按招式表里所记载的使用的。下面我们一起来学习,看看他们怎样把自己的招式记录进招式表。学习后你也可以创建自己的独门招式哦。
1.设计招式动作
招式不但要利害,而且还要好看。在战斗中那些华丽的招式是怎样做出来的呢?当然是画出来的,我们把一个角色的所有招式动作都记录在一幅招式图中(图1)。
在这幅招式图中,第0行的是角色在战斗场景中站着的样子和被攻击的样子。第1行是普通攻击的动作。以下各行则是角色所有的招式动作,每一行为一个招式。在记录一个招式时,我们要给出该招在招式图中排第几行,有多少帧,每一幅动作图的宽度和高度是多少。图1中角色闪闪发亮的那一个招式,在招式图中的第二排(从0排开始算),有四帧,每一幅动作图的宽度是64像素,高度是128像素。
2.为招式写脚本
在战斗中英雄使出一套华丽的招式后,敌人没有任何损失,你的属性也没有任何提升,那样的招式有什么用呢?所以要让你的招式既有姿势也有实际作用,不但要给出招式图,还需要给招式编写脚本。
这里我们又和脚本见面了,脚本在游戏中的应用是非常丰富的。例如,如果要削去敌人的生命值就可以在招式的脚本中给出减少生命值的脚本命令。具体写法相信大家在看了前面的脚本系统的讲解后,都不难完成。
3.招式也有类型
我们这里简单地把招式分为两种类型:攻击型和辅助型。攻击型的招式会给敌人造成伤害;辅助型的招式用于辅助我方成员,如为他们添加生命值或魔法值等等。
经过几个时辰后,阿志把武功秘籍都记熟了,还演练了好几次。
精灵阿比:“嗯,看来你都学会了。”
阿志:“当然!我把这几招都记录在我的招式表里了。”
精灵阿比:“嗯,现在你可以进入妖怪的老巢了。但要小心‘踩地雷’。”
阿志:“我也听客栈掌柜说过‘踩地雷’,这究竟是怎么回事?”
精灵阿比:“这是一个比方。你进入妖怪的老巢后,走在路上是看不见妖怪的,妖怪们会突然出现攻击你。这样的遇敌方式让你感觉就好像突然踩到地雷一样。”
阿志:“原来是这样,我会小心的。”
二、“踩地雷”是怎么产生的
在传统的RPG游戏中遇敌模式一般有两种:像《仙剑奇侠传系列》中可以看见敌人的可见式遇敌模式;《轩辕剑系列》中看不见敌人的“踩地雷”遇敌模式。我们这里要实现的是“踩地雷”模式。
“踩地雷”模式是指主角在场景中走着走着就突然进入战斗模式了,令人感觉防不胜防。激活战斗模式的要素可以有好多种,一般常见的是以下三种:一,计算主角走了多少步,当行走步数达到规定数目后便激活战斗模式;二,计算时间,到达指定时间便激活战斗模式;三,在场景中放置一些玩家看不到的透明人,当主角碰撞某个透明人后便激活战斗模式。
我们这里采用的是计算主角行走步数的方式。实现这个方式比较方便,为角色类添加一个成员变量m_nStepCount,用于累计角色所行走的步数,角色每行走一步就把这个变量加1,直到达到规定的步数后便激活战斗模式。
为游戏状态机添加一个状态GAMESTATE_FIGHT,把游戏状态设为GAMESTATE_FIGHT时就激活了战斗模式。
三、战斗系统的实现
阿志进入了妖怪的老巢后没多久却跑出来了。
精灵阿比:“怎么了?这么快就被吓回来了。”
阿志:“不是啊,我在妖怪的老巢里见识过了‘踩地雷’,但我却不能和妖怪展开战斗,所以跑回来问问你这是怎么回事。”
精灵阿比:“难道是冒险世界里还没有建立战斗系统?你等等,我现在就教你创建战斗系统。”
1.进入战斗系统
添加一个战斗系统类CFight,并添加一个成员函数Run(),游戏状态进入GAMESTATE_FIGHT后调用的就是这个Run()函数。首先要在Run()函数中放置一个状态机(图2)。
当然,在调用CFight的Run()函数前要告诉战斗系统,主角是跟哪一个妖怪开打,为战斗系统类添加一个函数SetRole(),它的原型为:
void CFight::SetRole(CRole* main, CRole* emeny);
这个函数很直观,就是接收主角角色指针和敌人角色指针。在这个函数里还要把Run()函数里的状态机状态设为FIGHTFSM_FIGHT,从而进入战斗状态。
为战斗系统类CFight添加一个函数Fight(),这是战斗函数。Run()函数的状态机处于FIGHTFSM_FIGHT状态时调用的就是这个战斗函数。
我们这里的战斗系统采用的是回合制的战斗,在战斗函数CFight()里放置一个状态机,称为战斗状态机。此状态机只有两个状态,分别是:“我方攻击”状态和“敌方攻击”状态(图3)。
在战斗系统类CFight中还需要两个坐标变量,分别标出主角在战斗场景中的坐标,和敌人在战斗场景中的坐标:
m_MainPos.x=MAIN_X; //设置主角坐标
m_MainPos.y=MAIN_Y;
m_EmenyPos.x=EMENY_X; //设置敌方坐标
m_EmenyPos.y=EMENY_Y;
有了坐标,我们还需要知道角色招式图中要显示出来的是哪一幅动作图。于是要在角色类中添加一个战斗图矩形变量,用来框出在战斗场景中应显示出招式图中的哪一个动作。
有了敌我双方两个坐标以及双方的战斗图矩形变量,我们就可以在战斗场景中把双方都显示出来了(图4)。
在Run()函数一开始就把战斗场景调出来,然后按双方当前的状态把双方的当前动作图也显示出来。
2.普通攻击的实现
准备了那么久,现在终于可以痛痛快快地打一场了!
先是我方发起攻击,攻击时会出现一个图5所示的战斗选项框。这里有三个选项,分别为:攻,普通攻击;招,使用招式攻击;物,使用物品。在战斗选项框中还用红色显示角色的生命值和用蓝色显示角色的魔法值。
给战斗系统类CFight添加一个函数FightOption()。在战斗函数CFight()中的战斗状态机处于“我方攻击”状态时调用的就是这个函数。
在函数FightOption()里还要放置另一个状态机(呼!好多状态机啊!)称为战斗选项状态机,它有四个状态:
FIGHTOPTION_NONE //什么也没选
FIGHTOPTION_ATTACK //攻击状态
FIGHTOPTION_GESTBOX //进入招式框
FIGHTOPTION_RESBOX //进入物品箱
现在先来看看如何实现普通攻击。我们已经知道,角色的普通攻击就是角色招式表里的第0行招式。为角色类CRole添加一个攻击函数Attack(),它的原型为:
int Attack(CRole* obj, int n);
obj 被攻击的角色对象指针
n 用哪一个招式
当点击战斗选项框的“攻”按钮时,把战斗选项状态设为FIGHTOPTION_ATTACK,进入这个状态后便调用攻击者的攻击函数Attack()。
在Attack()函数中,把被选中的招式的动作图一帧一帧地显示出来,因为在招式结构中已记录了每一个招式所在的行数和所要的帧数,还有每一个动作图的宽度和高度,所以可以很轻易地显示出来。如果没有把招式的每一帧的动作图显示完,该函数返回0;如果每一帧动作图都显示完了,该函数就返回1。
如果是普通攻击就执行:被攻击对象的生命值-(攻击者的攻击力-被攻击者的防御力);如果是招式攻击,就运行该招式的脚本。
使用以上方法计算的缺陷很明显,被攻击者每次被攻击都减去相同的生命值,变数太少。为了克服这个缺点,我们可以利用随机函数为攻击者的攻击力设置上下波动的范围。
如攻击者的攻击力为90,我们就可以设攻击者的最小攻击力为80,最大攻击力为95。被攻击者所受到的攻击就可以在85到95之间取一个随机值。当然,被攻击者的防御力也可以设最小防御力和最大防御力,这样战斗中的变数就更多了。CRole的Attack()函数代码下载地址:http://www.cpcw.com/33/game61.rar。
在执行Attack()函数前要把攻击者的坐标调到被攻击者的旁边,从游戏画面来看,要让攻击者能打到对方,否则攻击者就只会对着空气一阵乱打了。
当我方的Attack()函数返回1时,就要把我方的坐标设为原来的坐标,把战斗状态设为“敌方攻击”状态。
3.招式攻击的实现
单是普通攻击打起怪物来还是挺花时间,挺吃力的,最重要的是这样的战斗会让玩家觉得厌烦。这时我们可以使用招式攻击,强化角色的战斗能力,也为游戏添加一些趣味。
给战斗系统类CFight添加一个函数ShowGestBox(),用于显示角色的招式框。
点击战斗选项框的“招”便把选项设为FIGHTOPTION_GESTBOX,进入这个状态调用的便是ShowGestBox()函数。显示出来的招式框如图6所示。
在图6的招式框中可以看到,列表左边显示的是招式名称,列表右边显示的是使用该招式要消耗多少魔法值。当选中一个招式时,该招式的名称以蓝色显示,在招式框的左下角显示该招式的说明信息。
给战斗系统类添加一个成员变量m_nGestSl,用于存放当前选中的招式的索引值。它的值为-1时表示当前没有选中任何一个招式。当选中了一个招式,并按下招式框右下角的“使用”按钮后,先要检查攻击者当前的魔法值是否大于或等于使用选中招式所要消耗的魔法值。如果是就调用攻击者的攻击函数Attack()。
ShowGestBox()函数代码的下载地址:http://www.cpcw.com/33/game62.rar。
4.战斗中使用物品
在战斗中使用物品可是很重要的哦,游戏中的大魔王总是那么厉害,当我方队员被打得落花流水时,如果没有物品辅助的话就只有下次再来了。
给战斗系统类CFight添加一个函数ShowResBox(),用于显示物品箱。
点击战斗选项框的“物”按钮便把战斗选项状态设为FIGHTOPTION_RESBOX,进入后调用的就是ShowResBox()函数,图7就是显示的物品栏。
5.胜败乃兵家常事──战斗胜利和失败
游戏中的人生就像现实中的生活那样,我们的主角在经历了每一次战斗的洗礼后,并不是都能取得胜利。那在战斗之后如何来设置战斗的胜负呢?先来看看获胜的情况吧!
当敌方所有队员的生命值小于或等于0时,也就代表我方胜利了。把战斗系统类CFight的Run()函数里状态机的状态设为FIGHTFSM_WIN,我方胜利后一般可以得到一些物品、经验值。最后把游戏状态设为GAMESTATE_RUN便可以回到游戏场景了。
如果是输掉了战斗呢?我方所有队员的生命值小于或等于0时把战斗系统类CFight的Run()函数状态机的状态设为FIGHTFSM_LOST,如果输掉了战斗只好把游戏状态设为GAMESTATE_BEGIN重新开始游戏了。
本篇完整程序下载地址:http://www.cpcw.com/33/game63.rar。
勇敢的阿志在盘古大哥、精灵阿比等人的帮助下终于和妖怪交手了。经过了一轮轮的战斗,阿志也一步步深入老怪的巢穴。
一路不停的砍杀、一路的刀光剑影,直到最后杀死老怪,阿志不住地挥舞着手中的刀剑,思绪却飘了好远好远,往事一幕幕浮上心头……
从学习制作冒险世界的启动界面到盘古大哥扔给他地图编辑器,从场景的切换到学会在冒险世界中说话,从精灵阿比给他各类辅助的道具直到现在教会他武功秘籍……一路上有多少知名不知名的朋友的帮助,才走完了这艰辛的冒险之旅。阿志真想对一直支持他的朋友们说一声:“谢谢你们了!”
交流支持:电脑报论坛(bbs.cpcw.com)在疑难问题解答区有置顶的帖子供大家交流。






