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

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

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

  1. 《游戏脚本的设计与开发》-(RPG部分)3.6 队员列表和人物属性

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

  2. 《游戏脚本的设计与开发》-(RPG部分)3.8 通过脚本来自由控制游戏(一)

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

  3. 《游戏脚本的设计与开发》-(RPG部分)3.1 RPG地图到底怎么做?

    http://blog.csdn.net/lufy_legend/article/details/17417085 话说好久没有更新博客了,其实这段时间主要是工作忙,没时间.那又是什么刺激了我呢,为什 ...

  4. 《游戏脚本的设计与开发》-(RPG部分)3.4 地图跳转

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

  5. 《游戏脚本的设计与开发》-(RPG部分)3.5 游戏背包和任务系统

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

  6. 《游戏脚本的设计与开发》-目录序

    本系列文章目录 章节 标题 连接 序 游戏脚本简介 http://blog.csdn.net/lufy_legend/article/details/8888787 第一章 基本功能 1.1 读取和解 ...

  7. 《游戏脚本的设计与开发》-第一部分总结 文字脚本的功能扩展和一个游戏测试...

    脚本系列文章写了好几篇了,大家可能都不清楚这些脚本有什么用,游戏中如何能应用到这些东西.当然,目前所介绍的内容还只是个简单的开头,说到做游戏还远远不够.不过,本次就使用前几章所介绍的内容,先来尝试一下 ...

  8. 《游戏脚本的设计与开发》-1.1 读取和解析一个脚本文件

    上一篇<游戏脚本的设计与开发>-序中我介绍了游戏脚本的基本概念和准备工作,本篇来说说具体如何解析一个脚本 所谓解析脚本,就是按照自己定义的语法,将每一个脚本命令还原成不同的代码逻辑进行执行 ...

  9. 《游戏脚本的设计与开发》-第一章总结 文字脚本的功能扩展和一个游戏测试

    脚本系列文章写了好几篇了,大家可能都不清楚这些脚本有什么用,游戏中如何能应用到这些东西.当然,目前所介绍的内容还只是个简单的开头,说到做游戏还远远不够.不过,本次就使用前几章所介绍的内容,先来尝试一下 ...

  10. 《游戏脚本的设计与开发》-1.6 按钮,脚本的暂停和标签

    按钮 按钮在任何程序中都是必不可少的,本次先来看看如何脚本来实现按钮的各种功能.文章中要实现的几个脚本如下. /* 游戏脚本的设计与开发 第六章 */ //添加按钮 Button.add(layer0 ...

最新文章

  1. Python之sklearn:GridSearchCV()和fit()函数的简介、具体案例、使用方法之详细攻略
  2. CCNA学习笔记大全
  3. reg怎样存取注册表信息
  4. 马赛克,克星,真来了!
  5. 3-6-汉诺塔(Hanoi Tower)问题-栈和队列-第3章-《数据结构》课本源码-严蔚敏吴伟民版...
  6. PHP怎么做一个加法口诀,神奇的手指速算口诀,100以内的加减法一分钟学会
  7. c++ pat 乙级 ---1004 成绩排名
  8. DotNetOpenAuth实践之搭建验证服务器
  9. Collections常用方法总结
  10. 地面控制点的定义与作用_彩色透水混凝土路面在海绵城市建设中能起多大作用?...
  11. 【小5聊】sql server基础之查询经纬度范围,10公里范围的经纬度标注点
  12. element-ui+vue,翻页添加首页、尾页跳转按钮
  13. 简单飞机模型静态/模态分析
  14. 飞算soflu软件机器人-低代码的未来
  15. .net core 3.1JWT用户权限认证(二)获取token
  16. 【C语言】猴子吃桃问题。猴子第1天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第2天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想……
  17. 数学建模(2)--TOPSIS法
  18. 收集整理的openstack java封装 api的第三方实现的选择
  19. 【虚拟试衣论文笔记】CP-VTON+: Clothing Shape and Texture Preserving Image-Based Virtual Try-On
  20. 论文写作分析报告CSDN

热门文章

  1. Jetpack:Room配合LiveData/Flow使用优化,Room+Flow使用原理解析。
  2. 最全的“四大天王”合影集(绝对经典…
  3. 天啦噜!原来Android帧动画这么简单
  4. python降低图像分辨率_降低DDS图像的分辨率
  5. 11张好用的项目管理全流程图,支持下载保存(PMP项目管理可用)
  6. 启动界面、封面图片、Splash关不掉,一直转圈,无法进入
  7. Java实现仿win10计算器——微升级版
  8. 搜狐Q3由盈转亏:预计全年收入下滑12%,张朝阳归因于不确定性
  9. 桌面被关闭,如何在任务管理器中打开桌面?
  10. 很好用的搜网盘资源工具。很多都能找到