热门标签 | 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



推荐阅读
  • 本文详细介绍了如何通过配置 Chrome 和 VS Code 来实现对 Vue 项目的高效调试。步骤包括启用 Chrome 的远程调试功能、安装 VS Code 插件以及正确配置 launch.json 文件。 ... [详细]
  • 微信小程序支付官方参数小程序中代码后端发起支付代码支付回调官方参数文档地址:https:developers.weixin.qq.comminiprogramdeva ... [详细]
  • 本文介绍了两个重要的Node.js库——cache-content-type和mime-types,它们在处理HTTP响应头时非常有用。cache-content-type是基于mime-types构建的,并且实现了缓存机制以提高性能。 ... [详细]
  • iOS 小组件开发指南
    本文详细介绍了iOS小部件(Widget)的开发流程,从环境搭建、证书配置到业务逻辑实现,提供了一系列实用的技术指导与代码示例。 ... [详细]
  • 本文详细介绍了如何在VSCode环境中配置Prettier工具以支持TypeScript项目,同时结合ESLint实现代码风格的一致性和自动化格式化。 ... [详细]
  • 使用jQuery与百度地图API实现地址转经纬度功能
    本文详细介绍了如何利用jQuery和百度地图API将地址转换为经纬度,包括申请API密钥、页面构建及核心代码实现。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • 本文提供最新的CUUG OCP 071考试题库,包含70道题目,旨在帮助考生更好地准备Oracle Certified Professional (OCP) 考试。 ... [详细]
  • 本文探讨了Lua中元表和元方法的使用,通过具体的代码示例展示了如何利用这些特性来实现类似C语言中的运算符重载功能。 ... [详细]
  • 拖拉切割直线 ... [详细]
  • Web网络基础
    目录儿1使用HTTP协议访问Web2HTTP的诞生2.1因特网的起源2.2互联网、因特网与万维网2.3万维网与HTTP3网络基础TCPIP3.1TCPIP协议族3.2TCPIP的分 ... [详细]
  • 本文将详细介绍如何实现类似于CSDN博客的页面返回顶部功能,通过调整返回速度和图标显示条件,使用户体验更加流畅。适合前端开发者参考学习。 ... [详细]
  • 抽象工厂模式 c++
    抽象工厂模式包含如下角色:AbstractFactory:抽象工厂ConcreteFactory:具体工厂AbstractProduct:抽象产品Product:具体产品https ... [详细]
  • 本文探讨了SQLAlchemy ORM框架中如何利用外键和关系(relationship)来建立表间联系,简化复杂的查询操作。通过示例代码详细解释了relationship的定义、使用方法及其与外键的相互作用。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
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社区 版权所有