注意:本系列教程为长篇连载无底洞,半路杀进来的朋友,如果看不懂的话,请从第一章开始看起,文章目录请点击下面链接。
http://blog.csdn.net/lufy_legend/article/details/8888787
先给一位网友道个歉,答应上周更新的文章拖后了一周,本节来认识一下自动战斗系统。
先看一下效果预览:
所谓自动战斗系统就是战斗从开始到结束无需任何操作,其实自动战斗的胜负结果在战斗开始的时候已经决定了,战斗的画面只是还用来显示或者说回放这一战斗的过程,这种战斗方式开发成本较低,而且因为不用长时间的操作,很适合上班族们玩,所以这种战斗方式被广泛应用于页游中,比如《神仙道》,比如《三十六计》,再比如《修仙三国》。对于单机游戏来说,这种方式是不太可取的,但是我这个脚本最终也并不一定用来做单机,而且有朋友急着要一个战斗系统,我就先在这里简单的实现一下这种战斗,而传统的可操作的RPG回合制战斗方式我后面会花大功夫来讲解。当然即使是全自动战斗,要做完整也需要花一番功夫的,下面我要讲解的是远远不够的,所以这篇文章的标题是《战斗系统之自动战斗(一)》,等游戏中的其他功能都相对完善之后,我会再回头来继续完善这部分的内容。
因为自动战斗本身已经比较乏味了,如果没有各种绚丽的技能画面的花,那就显得很无聊了,虽然这次我只是简单的讲,但是也不能太简陋,先给人物加上特技属性,比如刘备。
"peo1":{ "Index":1,
"Name":"刘备",
"Lv":1,
"Exp":0,
"HP":200,
"MP":20,
"MaxHP":200,
"MaxMP":20,
"Force":78,
"Intelligence":76,
"Command":72,
"Agile":74,
"Luck":100,
"Face":1,
"R":1,
"RRect":[140,95,40,90],
"S":1,
"SRect":[0,0,64,64],
"SWidth":64,
"SHeight":64,
"Introduction":"刘备即蜀汉昭烈帝,字玄德,汉中山靖王刘胜的后代,三国时期蜀汉开国皇帝。",
"Skill":1
}
上面我给人物加上了Skill属性,然后添加一个新的配置文件skill.json。
https://github.com/lufylegend/lsharp/blob/3.7/script/initialization/skill.json
然后在一开始读取配置文件的时候把它读取近来。
https://github.com/lufylegend/lsharp/blob/3.7/index.html
显示特技属性的话,需要修改CharacterProperty.js文件,在切换到能力属性界面的时候,把特技加上去,代码不贴了,看这里。
https://github.com/lufylegend/lsharp/blob/3.7/Libraries/character/CharacterProperty.js
接下来遇到一个小难题,人物战斗用的形象没找到合适的,我这次就依然沿用《曹操传》格式的图片了。为了让之前建立的Character.js和Action.js能够通用,我在人物的设定中加上了SRect,SWidth,SHeight等属性,用来区分战场的形象在Character.js和Action.js中的设定。
在剧情画面中,显示人物的时候,我为了快速显示游戏画面,每个人物的每个动作都是先用一张静止的黑影来预先显示的,相应的图片读取完之后,会切换到读取后的图片,但是到了战斗画面中。人物攻击,受伤害等动作如果都是静止的图片的话,就不那么协调了。所以,我准备了下面的一套图片
它对应了下面的一套图片
然后,就需要根据人物设定文件中的设定来修改Character.js和Action.js这两个文件了,代码看下面。
https://github.com/lufylegend/lsharp/blob/3.7/Libraries/character/Character.js
https://github.com/lufylegend/lsharp/blob/3.7/Libraries/character/Action.js
接下来该进入战场了,准备好控制器,模型和视图。
https://github.com/lufylegend/lsharp/blob/3.7/Controllers/BattlemapController.js
https://github.com/lufylegend/lsharp/blob/3.7/Models/BattlemapModel.js
https://github.com/lufylegend/lsharp/blob/3.7/Views/BattlemapView.js
下面开始一点点看,一点点讲解。
控制器中
BattlemapController.prototype.cOnstruct=function(){ var self = this; LMvc.keepLoading(true); self.dataLoad();};BattlemapController.prototype.dataLoad = function(){ var self = this; self.model.dataLoad(self.outcomeLoad);};
模型
BattlemapModel.prototype.dataLoad=function(callback){ var self = this; //开始读取战场地图文件 var urlloader = new LURLLoader(); urlloader.parent = self; urlloader.addEventListener(LEvent.COMPLETE,function(event){ self.data = JSON.parse(event.target.data); callback.apply(self.controller,[]); }); urlloader.load("./script/battles/S"+LRPGObject.battleIndex+".ls"+(LGlobal.traceDebug?("?"+(new Date()).getTime()):""),"text");};
这部分是读取战场的配置文件,保存到模型中。
这个配置文件如下
{"enemys":[ {"index":3,"lv":"1"}, {"index":4,"lv":"1"}],"win":{ "exp":1234, "money":1000}}enemys表示敌方参战人员,win表示战斗胜利后得到的奖励。
BattlemapController.prototype.outcomeLoad = function(){ var self = this; self.model.outcomeLoad(self.imagesLoad);};模型
BattlemapModel.prototype.outcomeLoad=function(callback){ var self = this,i,self_arms,enemy_arms,characterData,member,obj; self.load_effect = []; self.arms = []; self.actiOns= []; self.self_arms = []; self.enemy_arms = []; var self_arms_coordinate = [{"x":200,"y":240},{"x":100,"y":140},{"x":100,"y":340},{"x":300,"y":140},{"x":300,"y":340}]; for(i=0;i这里开始就是重点了,这是自动战斗的计算过程,首先将我方的参战人员和敌方的参战人员,其中我方参战人员从我方的队伍中获取,按照一定的坐标保存到数组中,实际的页游中,一般都会有阵型等复杂操作,这时候,这里的坐标就要根据阵型来决定了,我先省略阵型等设定了,直接准备了一个坐标组。
把参战人员保存到数组中后,开始调用self._battleLoop函数,根据人物的速度来循环数组中的人员,决定攻击还是用特技等等动作。我这里把人物的morale属性当成速度了。
下面主要看如何来自动的进行战斗。
BattlemapModel.prototype._battleLoop=function(){ var self = this,i,j; for(i=0;ichara.hp())continue; var skill = chara.skill(); var count = 1; var targets = []; var addition = 1; if(skill){ var skillData = LMvc.datalist["skill"]["skill"+chara.skill()]; if(skillData && Math.random() <(skillData.Probability/100)){ var isAddEffect = false; for(j=0;j mytargets.length;j++){ self.actions[self.actions.length - 1].chara.push({"index":mytargets[j].chara.index(),"action":"stand"}); }*/ }else if(skillData.Type == 0){ addition = skillData.Addition/100; count = skillData.Count; } } } self.actions.push({"type":"action","chara":[{"index":chara.index(),"action":"attack"}]}); var dielist = []; var hertlist = []; var standlist = []; targets = self._getTargets(!data.self,count); for(j=0;j >> 0; hertlist.push({"index":targets[j].chara.index(),"action":"hert","num":num}); standlist.push({"index":targets[j].chara.index(),"action":"stand","num":num}); //self.actions[self.actions.length - 1].chara.push({"index":targets[j].chara.index(),"action":"hert","num":num}); targets[j].hert += num; if(targets[j].hert >= targets[j].chara.hp()){ dielist.push({"index":targets[j].chara.index()}); } } self.actions.push({"type":"action","chara":hertlist}); self.actions.push({"type":"action","chara":[{"index":chara.index(),"action":"stand"}]}); self.actions.push({"type":"action","chara":standlist}); if(dielist.length > 0){ self.actions.push({"type":"die","chara":dielist}); } if(self._getOutcome(data.self)){ return true; } } return false;};/*攻击伤害值计算*/BattlemapModel.prototype._getHertValue=function(attChara,hertChara){ var r; //得到攻击方的攻击力和等级 var attLv = attChara.lv(); var attAttack = attChara.attack(); //得到防御方的防御力 var hertDefense = hertChara.defense(); //攻击的伤害值计算 if(attAttack > hertDefense){ r = attLv + 25 + (attAttack - hertDefense)/2; }else{ r = attLv + 25 - (hertDefense - attAttack)/2; } if(r <1)r=1; r = ((110-Math.random()*20)*r/100) >>> 0; if(r <1)r=1; return r;};BattlemapModel.prototype._getTargets=function(value,count){ var self = this,arms,i,result = []; if(value){ arms = self.self_arms; }else{ arms = self.enemy_arms; } for(i=0;i arms[i].chara.hp())continue; result.push(arms[i]); } result.sort(function(a,b){return Math.random()>0.5;}); return result.slice(0,count);};BattlemapModel.prototype._getOutcome=function(value){ var self = this,arms,i,result = []; if(value){ arms = self.enemy_arms; }else{ arms = self.self_arms; } for(i=0;i arms[i].chara.hp())continue; return false; } if(value){ LGlobal.script.scriptArray.varList["OutcomeBattle"] = 1; }else{ LGlobal.script.scriptArray.varList["OutcomeBattle"] = 0; } return true;}; 因为在outcomeLoad函数中我用了
while(!result){ result = self._battleLoop(); i++; }所以当self._battleLoop返回false的时候,会一直进行循环,直到返回true表示战斗结束。在循环每个人物的时候,首先判断该人物是否已经阵亡,如下
if(data.hert > chara.hp())continue;如果没有阵亡,则该人物开始进行攻击,而攻击之前又判断是否发动特技攻击,所以先取得特技。var skill = chara.skill();然后进行判断,特技是否发动if(skill){ var skillData = LMvc.datalist["skill"]["skill"+chara.skill()]; if(skillData && Math.random() <(skillData.Probability/100)){...... } }每个人物攻击完之后,通过_getOutcome来判断,对方阵营的人员是否全部阵亡,从而来判断战斗是否结束,
if(self._getOutcome(data.self)){ return true; }仔细看 _battleLoop中的代码,你会发现,我把每次动作指令都保存到了actions这个数组中,指令分别有
"action","addHp","effect","die","over"这些指令,你也可以看作是一种脚本,最后显示战斗动画的时候,在控制器中会对这些指令进行解析,将它们变成动画。自动战斗的指令生成结束后,控制器读取其他的相应的文件,然后调用视图开始显示画面。
战斗开始后,控制器中,开始解析模型中保存的战斗指令
BattlemapController.prototype.checkAction=function(){ var self = this; var action = self.model.getAction(); if(action){ switch(action.type){ case "action": self.runAction(action); break; case "addHp": self.runAddHp(action); break; case "effect": self.runEffect(action); break; case "die": self.runDie(action); break; case "over": self.battleOver(action.result); break; } }};action表示动作改变,addHp表示加血,effect表示特技动画,die表示武将阵亡,over表示战斗结束。
各个指令的详细解析部分,还是直接看下面的代码吧。
https://github.com/lufylegend/lsharp/blob/3.7/Controllers/BattlemapController.js
自动战斗基本上就先这样了,下面看怎么进入战斗画面。
我们准备下面一段脚本
function characterclick3(); if(@task1010==1); RPGTalk.set(3,0,关羽的服务还好吗?); else; RPGTalk.set(1,0,少年,你能帮我捡肥皂吗?); RPGTalk.set(3,0,你是在消遣我吗?); RPGTalk.set(1,0,是的,少年,你能帮我捡肥皂吗?); RPGTalk.set(3,0,你要是能打赢我,我就让那边的关羽帮你捡肥皂。); RPGTalk.set(1,0,那就开战吧!); //进入战斗,参数战斗配置文件序号 RPGBattle.start(1); if(@OutcomeBattle==1); RPGTalk.set(3,0,竟然打败了我,那以后关羽就跟你了。); RPGTalk.set(2,0,猫了个咪的,关我什么事!?); RPGMember.add(2); Var.set(task1010,1); RPGMessageBox.show(关羽加入队伍。); else; RPGTalk.set(3,0,你还是帮我捡肥皂吧。); RPGTalk.set(1,0,这......); endif; endif;endfunction;这是一段刘备找基的过程,可以看到预计进入战斗画面,只需要下面脚本
//进入战斗,参数战斗配置文件序号 RPGBattle.start(1);脚本的解析部分如下LRPGBattleScript = function(){};LRPGBattleScript.analysis=function(value){ var start = value.indexOf("("); var end = value.indexOf(")"); switch(value.substr(0,start)){ case "RPGBattle.start": var params = value.substring(start+1,end).split(","); LRPGObject.RPGMap.showBattle.apply(LRPGObject.RPGMap,params); break; default: LGlobal.script.analysis(); }};还是那句话,战斗系统非常重要,我这里只是先来演示一下其中的一种方式,后面咱们再慢慢聊。
好了,大家一起帮助刘备来风流一下吧,测试链接。
http://lufylegend.com/demo/test/lsharp/rpg-lsharp-07/index.html
最后,给出本次的代码下载:
https://github.com/lufylegend/lsharp/archive/3.7.zip
预告:下一节会返回剧情部分,讲一下如何利用脚本来自由的控制画面中的人物,这将是任务系统的前提条件。
《游戏脚本的设计与开发》系列文章目录
http://blog.csdn.net/lufy_legend/article/details/8888787
本章就讲到这里,欢迎继续关注我的博客 转载请注明:转自lufy_legend的博客http://blog.csdn.net/lufy_legend