注意:本系列教程为长篇连载无底洞,半路杀进来的朋友,如果看不懂的话,请从第一章开始看起,文章目录请点击下面链接。
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;i chara.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;jmytargets.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