注意:本系列教程為長篇連載無底洞,半路殺進來的朋友,如果看不懂的話,請從第一章開始看起,文章目錄請點擊下面連結。
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
本章就講到這裡,歡迎繼續關注我的部落格
轉載請註明: