《游戏脚本的设计与开发》-(RPG部分)3.7 战斗系统之自动战斗(一)

注意:本系列教程为长篇连载无底洞,半路杀进来的朋友,如果看不懂的话,请从第一章开始看起,文章目录请点击下面链接。

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<LRPGObject.memberList.length;i++){
		obj = {"chara":LRPGObject.memberList[i],"action":"stand","direction":"right","coordinate":self_arms_coordinate[i],"hert":0,"self":true};
		self.arms.push(obj);
		self.self_arms.push(obj);
	}
	self_arms_coordinate = [{"x":600,"y":240},{"x":500,"y":140},{"x":500,"y":340},{"x":700,"y":140},{"x":700,"y":340}];
	for(i=0;i<self.data.enemys.length;i++){
		characterData = LMvc.datalist["chara"]["peo"+self.data.enemys[i]["index"]];
		member = new MemberData(characterData,self.data.enemys[i]["lv"]);
		obj = {"chara":member,"action":"stand","direction":"left","coordinate":self_arms_coordinate[i],"hert":0,"self":false};
		self.arms.push(obj);
		self.enemy_arms.push(obj);
	}
	self.arms.sort(function(a,b){return b.chara.morale() - a.chara.morale();});  
	var result = false;
	i=0;
	while(!result){
		result = self._battleLoop();
		i++;
	}
	self.actions.push({"type":"over","result":LGlobal.script.scriptArray.varList["OutcomeBattle"]});
	callback.apply(self.controller,[]);
};

这里开始就是重点了,这是自动战斗的计算过程,首先将我方的参战人员和敌方的参战人员,其中我方参战人员从我方的队伍中获取,按照一定的坐标保存到数组中,实际的页游中,一般都会有阵型等复杂操作,这时候,这里的坐标就要根据阵型来决定了,我先省略阵型等设定了,直接准备了一个坐标组。

把参战人员保存到数组中后,开始调用self._battleLoop函数,根据人物的速度来循环数组中的人员,决定攻击还是用特技等等动作。我这里把人物的morale属性当成速度了。

下面主要看如何来自动的进行战斗。
BattlemapModel.prototype._battleLoop=function(){
	var self = this,i,j;
	for(i=0;i<self.arms.length;i++){
		var data = self.arms[i];
		var chara = data.chara;
		if(data.hert > 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;j<self.load_effect.length;j++){
					if(self.load_effect[j] == skillData.Effect){
						isAddEffect = true;
						break;
					}
				}
				if(!isAddEffect)self.load_effect.push(skillData.Effect);
				self.actions.push({"type":"effect","effect":skillData.Effect,"charaIndex":chara.index()});
				if(skillData.Type == 1){
					var mytargets = self._getTargets(data.self,skillData.Count);
					self.actions.push({"type":"addHp","chara":[]});
					for(j=0;j<mytargets.length;j++){
						mytargets[j].hert -= skillData.HP;
						if(mytargets[j].hert < 0)mytargets[j].hert = 0;
						self.actions[self.actions.length - 1].chara.push({"index":mytargets[j].chara.index(),"num":skillData.HP});
					}
					/*
					self.actions.push({"type":"action","chara":[]});
					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<targets.length;j++){
			var num = self._getHertValue(chara,targets[j].chara)*addition >>> 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.length;i++){
		if(arms[i].hert > 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.length;i++){
		if(arms[i].hert > 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


  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值