標籤:遊戲開發
現在在做的項目大致分為兩塊:戰鬥系統和除戰鬥系統之外的(簡稱外圍系統),而我一直在做的是外圍系統的開發,至少在6月份返校畢業答辯之前沒有動過戰鬥系統。答辯回來之後很長一段時間內也是在做外圍系統的bug修複,可是由於種種原因項目趕不上所謂的進度了,上周五主管問我和另外一個也主要負責外圍系統開發的同事誰更忙,我一句我沒啥事幹,結果主管說戰鬥系統的主動技能讓我來做。這周一開會負責人一紙任務安排扔下來,上面寫著XX同事這周完成主動技能的開發及相關係統的bug修複,還說沒完成任務就XXX,此處省略XX字。
再稍微說下目前本人的情況:不是電腦相關專業畢業的,去年學了兩個月C++,一個月Cocos2dx,然後十月份入職現在的這個公司,因此相關技能水平就。。。現在在做的項目加上被砍掉的項目,共經曆過兩個項目開發。本著一腔熱情加上強大的互連網資源,到目前為止項目組安排的任務都按時完成了。所以可以說是個稍微合格的遊戲開發從業者吧。
扯了這麼遠回到正題吧。技能的設計,首先我來理解一番遊戲中的技能。在回合制遊戲中,主動技能的流程大致為:釋放技能,得到技能影響值(項目組天天喊的buff)並確定技能對象,技能檢測+影響值添加到技能對象。而這次的主動技能設計我也是照著這個思路來的,因此本文可以據此大致分為三塊來講:技能釋放部分,buff產生部分(不添加至對象),技能檢測部分。(本項目的技能相對來說較簡單,技能釋放不考慮魔法值,buff產生不考慮別的影響因素,技能檢測不涉及命中率)
戰鬥:
己方隊伍+敵方隊伍,說白了就相當於一個卡牌遊戲。
一、釋放技能
本項目的釋放流程大致為:戰鬥情境(BattleScene)中,當前操控的己方隊員達到技能CD時間,玩家主動執行某個操作釋放當前戰鬥隊員的技能。釋放的技能可能是一個組合技能也可能是單個技能。
在這裡要控制的是:技能資訊(影響值、技能釋放者、技能作用者、技能影響回合),技能管理(觸發檢測、產生技能影響、清除影響)。因此我首先想到的時候要在該情境裡產生一個技能管理器,負責處理這裡要做的技能控制。
寫了一個技能管理類:skill_command(稱為技能控制器吧),大致成員如下:
class skill_command{public: /** *這個成員函數傳回值用來做對是否是即時生效型技能的判讀 **/ bool init(); private: skill_info _info; //技能的資訊 role*_user; //技能使用者 role*_obj; //技能作用者}
因為技能是在BattleScene釋放,且需要在該情境下做一系列的檢測,所以我的想法是將技能管理作為該情境內的成員,在該情境內執行技能管理類的檢測函數。而考慮到技能可能是組合技能,所以在情境類中,我以一個vector儲存技能管理對象。
class BattleScene{private:vector<skill_command*> vecSkillCmd;}
採取了這樣的設計之後,每一次操作隊員釋放某個技能,先分解技能組合,產生一個技能資訊則new一個指標儲存在vecSkillCmd中:
vec = analyze(skill)for(//做個迴圈){auto skill_cmd = new skill_command(); skill_cmd->init();//在這裡還需要設定技能資訊及作用對象等 vecSkillCmd.push_back(skill_cmd)}
做這樣的設計考慮的是:釋放一個技能,將其組合技能分解出來單獨控制,化繁為簡。也因為我已經得到了一個技能產生作用需要的資訊(記錄了技能資訊、技能作用者和使用者),所以我在技能作用、檢測階段就不需要額外再向使用者需要諸如技能對象等資訊,只需要在情境中提供檢測條件即可,條件達成了就可以直接在skill_command內部實現技能影響。想當初上一個項目我也參與到了戰鬥系統後期的維護工作,做的一塊就是最佳化技能的效果表現,當初碰到最大的麻煩就是很難確定技能對象,所以那時候好苦逼的在找技能對象。所以這一次我的想法是,不管技能如何我首先要做的就是拆解開來,單獨的確定其使用者和作用者,這樣每次一次做技能檢測和產生技能效果我都能找到對象。
設計案中也提到,技能可以分為即時作用和延時作用。指的是某些技能是一使用就會產生效果(如發射子彈、發射雷射等),而有些是需要達到某個條件(如碰撞到了敵方隊員)才會產生效果。所以基於化繁為簡的考慮,在分解技能資訊,產生技能控制器的階段我就將這兩類技能分開處理。具體做法是在BattleScene情境內再添加一個成員專門用來儲存即時作用的技能:
class BattleScene{private:vector<skill_command*> vecSkillCmd; vectpr<skill_command*> vecInstanceSkillCmd;}
這樣處理之後,在產生技能控制器階段依據一技能生效類型分開儲存至不同的管理容器內。在檢測階段就只需要在不同的條件階段,迴圈檢測不同的容器內容,就可以達到我們的設計目的。如對於即時生效的技能,我只需要在釋放完技能後對vecInstanceSkillCmd的成員做檢測即可。當然會說直接將vecSkillCmd放在這裡檢測不就可以了嗎?這就是我如何將一個技能管理器skill_command儲存的考慮了。
上文提到了,依據技能的作用類型分別會將技能控制器儲存至vecSkillCmd和vecInstanceSkillCmd。這裡考慮了其作用時間,同時我也考慮了其作用條件。因為有的技能是即時無條件作用,而有的是即時有條件作用。針對即使無條件作用的,因為此種技能即時無條件作用了,所以我不需要在釋放完之後再做管理,因此我沒有儲存至vec中。而即時有條件作用的才會儲存至vec中以備之後做檢測,這裡我們用了一個成語函數releaseSkill表示技能釋放動作:
void BattleScene::releaseSkill(){ vec = analyze(skill); for(//做個迴圈) { auto skill_cmd = new skill_command();//產生一個技能控制器 skill_cmd->init();//在這裡還需要設定技能資訊及作用對象等 if(//如果不是立即生效) { vecSkillCmd.push_back(skill_cmd); } else//即時生效 { if(//無條件觸發) //執行觸發效果 { skill_cmd->triggerSkill(); CC_SAFE_DELETE(_cmd);//刪除該指標 } else { vecInstanceSkillCmd.push_back(skill_cmd); } } }}
進行到這一步,關於技能的釋放部分就差不多了。而在項目中我做的技能釋放也就是這樣一個操作。
二、buff產生
其實上周五安排我做技能的時候,我一開始想的是如何做buff的產生。同事也提到要寫個通用的介面,可以做道具的處理(加血、加速等道具),這樣就可以省時省力。所以首先做的工作便是做buff,當然也沒辦法,因為那個時候沒策劃跟進我該如何做技能,直到這周三勉勉強強才把方案拿過來,此處又要省略XX字了。
因為我們已經有了一個role角色類,我想到buff不就是加到角色上的麼,因此我的設計方案是如之前寫技能控制器一樣,在role角色類內新增成員作為buff控制器。也是基於化繁為簡的考慮,一個buff就產生一個控制器,buff控制器內部也是包含buff資訊,作用者和使用者等資訊:
class buff_command{private: buff_info_info; //buff的資訊 buff_user_user;//buff的使用者 buff_obj_obj;//buff的作用者}
每一次產生了一個buff,就產生一個控制器,與技能控制器的設計思路類似:在role角色類內部以容器管理這些控制器,每次使用技能或者使用道具而得到了一個buff,就產生一個buff儲存至容器內,以便做後面的技能檢測和buff效果產生:
class role{public: void addBuff();private: vector<buff_command*> vecBuffCmd;}
role角色類一個buff管理成員和一個buff的產生函數。buff的產生函數在技能控制器的初始化函數內部調用,其大致內容如下:
void role::addBuff(){//檢測重複buffcheck();auto buff_cmd = new buff_command();buff_cmd->init();vecBuffCmd.push_back(buff_cmd);}
跟技能控制器的產生類似,這裡有一步的內容是做buff重複性檢測:已經觸發的buff刪除其buff效果,而沒有觸發的則直接從容器中刪除。
結合第一部分的內容大致做個流程介紹:
1、首先在SceneBattle情境觸發技能使用:
battle_role->releaseSkill();
2、觸發技能使用之後解析技能組合產生技能控制器,初始化技能控制器資訊:
auto skill_cmd = new skill_commant();skill_cmd->init();
3、產生技能buff:
bool skill_command::init(){initSkillInfo();//技能作用對象產生buff資訊skill_obj->addBuff();} 經過以上三步,簡單的一個buff便已產生。
三、技能檢測
這一部分我感覺有點複雜,也是我拿不準的地方。周六剛測試了一遍整個釋放-添加-檢測邏輯,在現有的幾套技能中是沒有問題的,就是不知道之後的會怎麼樣。
項目的技能檢測設計為:首先要檢測觸發條件,再檢測生效條件。既首先一個技能要因某個條件而被觸發,如己方隊員碰了一下對方,或者己方隊員碰到了某個情境元素,那麼該技能便達到了觸發條件;滿足了觸發條件之後,接著去做技能的生效條件,如該技能是要釋放者碰到了對方才會釋放,或者碰撞到了己方隊友才會釋放等等。所以在技能控制器skill_command內部寫了兩個成員函數:
class skill_command{public: //用來做觸發條件的檢測 void checkTriggerCondition(); //達到觸發條件了,則觸發技能 void triggerSkill();private: bool _bOk;}
技能的檢測主要就是靠這這兩個成員函數來實現,當然實際上這兩個函數是帶參數的。調用函數的地方是在BattleScene情境中:
BattleScene:://某個動作for(//){auto skill_cmd = vecSkillCmd[i];//每次先做條件檢測,達到條件了之後控制器內部的bool型成員為trueskill_cmd->checkTriggerCondition();//內部bool型成員為true的時候執行該成員函數的內部動作skill_cmd->triggerSkill();}
因為之前的設計中在技能控制器內部有技能作用者這一成員_obj,所以實際上技能的觸發是調用_obj的成員函數來實現:
bool buff_command::triggerSkill(){if(_bOK){_obj->triggerBuff();}}
而我設計的觸發buff這個成員函數,裡面的實際動作是對role這個類裡面的buff容器進行管理:
void role::triggerBuff(//帶能找到該buff的一個參數){for(//){if(//是該buff){auto buff_cmd = vecBuffCmd[i];buff_cmd->trigger();}}} 這一步便實現了一個buff的觸發。回到我們之前提到的buff_command這個控制器:
class buff_command{public: void trigger();private: buff_info_info; //buff的資訊 buff_user_user;//buff的使用者 buff_obj_obj;//buff的作用者} 成員函數trigger實際上是對buff的作用者_obj執行buff結算。依據buff類型(血、速)等,對角色的相應數值進行調整。而在實際的設計過程中,考慮到buff資訊的獨立性,以一個獨立的結構體:buff_info作為role的成員變數,每次做buff結算的時候實際上調整的是該成員的值,然後再將這些值添加到角色role的相應資訊上去。這樣相對獨立的設計,一方面適合直觀的展現每次技能使用帶來的技能影響(當然是對我們開發人員來說);一方面因為是改別人的代碼,這樣獨立的設計一個buff資訊不會影響到已有代碼所實現的功能(改代碼好苦逼,主要是容易偏離原有設計者的思路)。
這樣buff的檢測+產生效果功能便已實現,最後的就是buff的清理工作了。
回合制遊戲的一大特點就是一回合一回合的走下去,buff的影響效果也是有回合影響的;而技能的釋放也只是會在當前回合有效:即釋放了一個技能,只會在當前回合做有效性的檢測——vecSkillCmd/vecInstanceCmd執行成員的內部檢測函數,而當前回合結束後要刪除所有成員(因為釋放動作已經結束了,當然還要記憶體釋放)。
技能控制器的控制很簡單,只需要在每次回合結束的時候清理vecSkillCmd的成員及記憶體釋放,而vecInstanceCmd由於是管理的即時生效型技能,所以這個應該在釋放完技能(即技能特效播放完)就需要清理及釋放。
而對於buff控制器則稍微複雜點,因為涉及到buff有沒有被觸發:
1、對於觸發了的buff,要做回合檢測:達到了回合限制則開始清理buff——調整角色類role的成員buff_info的值(即清理buff的影響值,假如造成了10點扣血,則清理的時候要加回10點血)。這裡可能需要解釋一番buff的生效機制:假如觸發了10點扣血的buff,那麼role的成員buff_info裡面的_hp數值就要調整為-10,表示扣血。每次對角色的數值資訊進行結算的時候加上了這個buff值-10,就相當於實現了扣血功能,所以在清理buff的時候要加回10,buff_info裡面的_hp數值就成為了0.
2、對於沒有觸發的buff,則直接清理刪除掉,這個跟技能控制器的清除管理思路類似。
這個清理工作是在每回合結束時進行,清理函數設計為角色類role的成員函數,對於情境中的每個角色都執行內部設計的清理檢查函數。
至此這一周所做的工作便是這些了,寫這篇部落格相當於是對一周工作的總結,也不知道設計上哪些地方不合理了。不合理也木有辦法,我所會的就是這些了。。。其實本來還想結合設計模式相關的知識來梳理一番這周的工作,可是還沒有看相關的書籍所以不好怎麼去梳理。總之設計模式相關方面的書肯定是要看的,最近在看《STL源碼剖析》和《Programming in Lua 3》這兩本書,看完其中一本之後便需要看關於設計模式相關的資料了。
總結:
在寫完之後其實自己也想了一些改進:針對buff的最終影響類型——血、速、攻、魔等,寫不同的類,然後統一讓buff_command進行管理,這樣是否便達到了可擴充的要求呢?今後如果還需要有額外的buff影響,那麼只需要再添加額外的類讓buff_command類進行管理即可?
不管這次的設計思路如何,總歸是對自己的一種鍛煉吧,之前畢竟沒做過這些而今後總是會要做這些工作的~所謂的一些術語如:代碼耦合度、代碼複用、可擴充性等都是需要進一步的理解,在工作中需要再多注意的。
遊戲開發——戰鬥系統之技能設計