游戲開發——戰斗系統設計技巧


       :戰斗系統和除戰斗系統之外的(簡稱外圍系統)。而我一直在做的是外圍系統的開發,至少在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()
{
	//檢測反復buff
	check();
	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型成員為true
	skill_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信息不會影響到已有代碼所實現的功能(改代碼好苦逼。主要是easy偏離原有設計者的思路)。

        這樣buff的檢測+產生效果功能便已實現,最后的就是buff的清理工作了。

        回合制游戲的一大特點就是一回合一回合的走下去。buff的影響效果也是有回合影響的;而技能的釋放也僅僅是會在當前回合有效:即釋放了一個技能,僅僅會在當前回合做有效性的檢測——vecSkillCmd/vecInstanceCmd運行成員的內部檢測函數,而當前回合結束后要刪除全部成員(由於釋放動作已經結束了。當然還要內存釋放)。

        技能控制器的控制非常easy,僅僅須要在每次回合結束的時候清理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類進行管理就可以?

       無論這次的設計思路怎樣。總歸是對自己的一種鍛煉吧,之前畢竟沒做過這些而今后總是會要做這些工作的~所謂的一些術語如:代碼耦合度、代碼復用、可擴展性等都是須要進一步的理解,在工作中須要再多注意的。




版權聲明:本文博客原創文章,博客,未經同意,不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM