热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

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

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

本章就讲到这里,欢迎继续关注我的博客

转载请注明:转自lufy_legend的博客http://blog.csdn.net/lufy_legend



推荐阅读
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 本文详细介绍了MySQL表分区的创建、增加和删除方法,包括查看分区数据量和全库数据量的方法。欢迎大家阅读并给予点评。 ... [详细]
  • 本文介绍了一些Java开发项目管理工具及其配置教程,包括团队协同工具worktil,版本管理工具GitLab,自动化构建工具Jenkins,项目管理工具Maven和Maven私服Nexus,以及Mybatis的安装和代码自动生成工具。提供了相关链接供读者参考。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文总结了Java中日期格式化的常用方法,并给出了示例代码。通过使用SimpleDateFormat类和jstl fmt标签库,可以实现日期的格式化和显示。在页面中添加相应的标签库引用后,可以使用不同的日期格式化样式来显示当前年份和月份。该文提供了详细的代码示例和说明。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
author-avatar
Z张海男_851
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有