基于ECS模型的卡牌戰(zhàn)斗框架3

上一章將實現(xiàn)戰(zhàn)斗邏輯的ECS模式進行了記錄,這一章開始記錄游戲邏輯內(nèi)的幾個大的業(yè)務模塊。

這幾個模塊是經(jīng)過多個項目驗證,逐步走向相對完善的一套機制,也許無法涵蓋所有的游戲類型,但是應當具有或多或少的參考意義,需要在具體實現(xiàn)時加以甄別和考量。

核心通用業(yè)務數(shù)據(jù)設(shè)計與實現(xiàn)

如果想構(gòu)建一套成功的框架能夠?qū)崿F(xiàn)游戲內(nèi)絕大多數(shù)的業(yè)務邏輯,那么就需要經(jīng)過抽象,將業(yè)務邏輯進行細分,直到某一層的內(nèi)容通過組合就可以實現(xiàn)業(yè)務邏輯所要求的功能。

基于此思路,所以我設(shè)計了幾個通用的業(yè)務模塊與數(shù)據(jù)結(jié)構(gòu)?;蛟S我們對大的業(yè)務模塊依舊需要各種層級設(shè)計,但是對于它們的邏輯部分,則完全可以通過通用業(yè)務模塊的組合實現(xiàn)。

需要說明,我們還需要設(shè)計一些通用枚舉數(shù)據(jù),這部分內(nèi)容就在下面具體用到的模塊內(nèi)進行簡易說明。

敵我識別的設(shè)計

游戲戰(zhàn)斗中,最核心的功能之一便是需要區(qū)分敵我,因而就需要敵我識別模塊

最簡單的敵我識別,就是給每個實體添加一個id用以表示隊伍,然后在模塊核心存放隊伍之間的關(guān)系。當需要判斷兩個實體的關(guān)系時,取出隊伍id即可判斷。需要指出,這里的隊伍需要依據(jù)自身項目需求進行設(shè)計,并不指向字面意思的隊伍。以我的游戲舉例,那么一個玩家所能控制的所有實體歸為一個隊伍,所有NPC實體歸為一個隊伍。類比守望先鋒,那么一個玩家控制的角色就是一個隊伍。

現(xiàn)給出我的項目中關(guān)系枚舉的類型說明:

關(guān)系枚舉 關(guān)系說明
自己 唯一id相同的實體,即為自己
我方 兩個實體所屬隊伍id為同一個的,即相互為我方實體
友方 兩個實體所屬隊伍id為友方關(guān)系的,即相互為友方實體
敵方 兩個實體所屬隊伍id為敵對關(guān)系的,即相互為敵方實體
中立 兩個實體所屬隊伍id為中立關(guān)系的,即相互為中立實體

當我們站在游戲開發(fā)者的角度開發(fā)游戲時,所有的關(guān)系可以分為兩種視角來看待:

  1. 玩家視角:即我們將自己帶入玩游戲的玩家,那么很自然的就可以設(shè)定大多數(shù)NPC都是敵方;
  2. 上帝視角:即所有的實體相互之間都存在關(guān)系,而他們與我們是沒有關(guān)系的,我們不可以代入特定關(guān)系去開發(fā)邏輯。

上述敵我識別的枚舉雖然具有通用性,但在具體項目開發(fā)中,我依舊推薦使用上帝視角來看待我們寫的每一行代碼。只有我們足夠抽身出具體邏輯,我們的代碼才足夠穩(wěn)健,而不會因關(guān)系判斷異常而導致各種難以發(fā)現(xiàn)的隱藏小bug。

通用條件

條件判斷是我們業(yè)務邏輯內(nèi)核心的一部分。我們經(jīng)??吹接螒騼?nèi)技能描述的部分,會附帶若干生效前提,這就是條件。如何將條件抽離的足夠干凈成為一個獨立模塊,就是下文要說明的內(nèi)容。

在我的項目中,因開發(fā)階段所涉及內(nèi)容的關(guān)系,到頭來也只是分為了三個條件類型:

  1. 隨機:填寫隨機概率,表示該條件有多少的概率為通過,除此之外再無其他限制;
  2. 公式判斷:直接填寫條件公式,按照自定義解釋器認可的公式格式,大多數(shù)數(shù)據(jù)以判斷某個傳入實體的指定屬性是否滿足條件值為主;
  3. 指定范圍存在指定角色:通過填寫形狀、形狀位置、指定角色id,若指定范圍內(nèi)有指定角色存在,即條件判斷成功。

由此可見,通用條件的數(shù)據(jù)結(jié)構(gòu)設(shè)計核心就是條件類型(依據(jù)項目需求而設(shè)計),而其他數(shù)據(jù)的設(shè)計則為條件類型服務。

公式判斷是一個很重要且具有通用性的條件類型,其價值等同于隨機條件,可以在任意類型游戲中進行設(shè)計。而在公式中,我們會涉及一個很重要的枚舉,就是屬性枚舉。只有有了屬性枚舉,我們才可以獲取實體的某個具體的屬性去進行邏輯判斷或生效。

公式判斷也引出我自定義的解釋器。

自定義解釋器簡介

這套自定義解釋器源于我很久之前與熱更抗衡的一個想法。核心戰(zhàn)斗無法熱更是一些項目無法忍受的,那么作為折中,可以開發(fā)一個足夠小而不必引入插件的語言,使策劃通過他配置諸如傷害公式這些重要而又足夠小的代碼。

正是基于這個思路,我開發(fā)了這套簡易的解釋器,暫稱它為CH。這個解釋器并不復雜,支持加、減、乘、除、余與邏輯或、且、非,以及條件、括號運算。解析公式后生成語法樹即可運行出結(jié)果,返回結(jié)果可以是double或bool類型。這里比較重要的,其實是對于運算節(jié)點的解析,舉一個傷害公式的例子:

(1+S.P_801)/(1+T.P_901)+(S.P_803-T.P_908)

上述公式中,S、T表示的是這個傷害公式所作用的攻擊方與被擊方,P_801的表現(xiàn)形式表示的是獲取對應實體的id為801的屬性。所以如何解析出這個含義就是需要額外開發(fā)的內(nèi)容。

在項目中,通過繼承解釋器,就可以對傳入的Token進行單獨解析。一旦發(fā)現(xiàn)Token不是數(shù)字,就可以按照規(guī)定的模板去解析Token的內(nèi)容,并返回可以構(gòu)建語法樹的對應的節(jié)點。這里就不再展開敘述。

至于執(zhí)行解析完成的公式,需要我們先將一些會用到的參數(shù)傳入公式對應的數(shù)據(jù)堆中,再執(zhí)行語法樹,即可根據(jù)公式內(nèi)容返回計算獲得的結(jié)果。

我這套自定義解釋器除了條件會用,后續(xù)行為、過濾等模塊也都會有用到。

通用過濾

過濾模塊是通用模塊中的基礎(chǔ)之一,對實體的篩選都會用到通用過濾的內(nèi)容。

通用過濾核心用到下面幾個字段:

字段 類型 說明
敵我識別 敵我識別枚舉 基于使用通用過濾的場景判斷敵我
實體類型 實體類型枚舉 實體類型作為業(yè)務邏輯賦予實體的數(shù)據(jù),根據(jù)該值進行過濾
公式條件 表示條件的公式字符串 使用公式篩選目標是否滿足過濾條件

通用過濾被用到的地方,大抵是要遍歷實體的,然后對所有實體進行過濾條件篩選,篩選通過的實體就是過濾結(jié)果。

通用行為

通用行為是通用模塊內(nèi)的核心之一,所有的復雜業(yè)務邏輯模塊都需要用到行為。當我們需要向選中的角色修改屬性、添加buff、觸發(fā)特殊技能,都可以通過通用行為的模式去執(zhí)行。

通用行為數(shù)據(jù)的核心,就是其行為類型。依據(jù)業(yè)務邏輯內(nèi)容的不同,行為類型也可以有很多,下面就通過表格列舉常見的行為類型及其所需的參數(shù):

行為 所需參數(shù)
屬性輸出 輸出的屬性類型、輸出值的計算公式
輸出buff buff的id
刪除buff buff的id
添加技能 技能的id
創(chuàng)建子物體(子彈) 子物體的id

上面的幾種常見的行為類型,基本適用所有的戰(zhàn)斗類型。而游戲本身特殊的業(yè)務邏輯,就需要在這些的基礎(chǔ)上再定義新的行為類型,以涵蓋業(yè)務需求。

而在代碼實現(xiàn)層面上,我們需要根據(jù)不同的行為類型去派生對應的Action以執(zhí)行該行為類型所對應的邏輯(注意,此處的Action指的是通用行為類,而非顯示層處理邏輯消息的Action模塊)。

通用選擇目標

有了行為,我們還需要選出行為所作用的目標實體,通用選擇目標就是做這件事的通用模塊。

不同的業(yè)務邏輯,通用選擇目標數(shù)據(jù)也會有差別,但是核心流程不會變。

通用選擇目標流

特定實體集合是一個抽象的概念,需要結(jié)合業(yè)務邏輯進行定義。以我們項目為例,我們設(shè)定有“場上全體角色”、“場上我方全體角色”、“自身實體”等選項。需要特別說明,這里的“我方”、“敵方”等概念,都是基于執(zhí)行選擇目標的實體而言的,我們開發(fā)者仍然是作為第三視角(上帝視角)來審視與實現(xiàn)功能的。

從實現(xiàn)性能角度考慮,通過選取場上全體角色再配合過濾數(shù)據(jù),一樣可以選出場上我方全體角色這一目標,但是硬編碼一般可以實現(xiàn)更好的效率,因而這里的數(shù)據(jù)配置需要策劃慎重填寫,除非項目上線新增數(shù)據(jù)邏輯。另外,指定實體范圍越小,則預先篩選出的實體越少,而后續(xù)的過濾等判斷都是需要對實體進行循環(huán)判斷的,所以實體越少對性能的提升也就越高。這里之所以著重強調(diào)性能的問題,是因為在實際項目中,選擇目標是基礎(chǔ)通用模塊中運行頻率最高的模塊,所以這里的性能優(yōu)化會直接影響游戲的性能。

根據(jù)業(yè)務需要,上面的只是一個基本流程,還可以在對應點位內(nèi)添加新的選擇條件以實現(xiàn)自身項目的業(yè)務需求。比如在通用過濾之后,可以添加依據(jù)指定形狀對實體的位置進行篩選,以此可以實現(xiàn)場上陷阱的目標選擇邏輯。

核心復雜業(yè)務邏輯架構(gòu)

技能

技能是任何一個帶有戰(zhàn)斗邏輯的游戲所必不可少的模塊。小則僅僅是播放一個動作扣一次血,大則可能因各種技能相互作用而產(chǎn)生一整套完整的戰(zhàn)斗流程。

根據(jù)過往經(jīng)驗,我將技能分為主動與被動兩種:

  • 主動技能具有時間屬性,即隨著時間的流逝,技能邏輯逐漸生效,其生效邏輯與運行時間有強關(guān)聯(lián);

  • 被動技能沒有時間屬性,可以理解為瞬發(fā)技能。

主動技能通過配置可以實現(xiàn)瞬發(fā)技能的效果,但是被動技能本身因其自身的觸發(fā)機制而與主動技能表現(xiàn)出不同的邏輯流程。

下面進行詳細說明。

主動技能

主動技能有一套通用的設(shè)計結(jié)構(gòu),可以將技能邏輯分為技能、技能因子兩部分:

  • 技能部分:技能部分包含n個技能因子,同時還擁有技能的通用數(shù)據(jù),比如技能CD、基礎(chǔ)選擇目標規(guī)則等。具體應該包含哪些數(shù)據(jù)需要根據(jù)項目需求進行設(shè)計,但很明顯的是技能部分包含的是整個技能釋放期間都不變的規(guī)則或概念數(shù)據(jù)。
  • 技能因子:技能因子是技能真正生效的部分,這里可以包含有具體的生效時間、生效邏輯、對應動作信息等。

其實在具體項目的技能設(shè)計時,技能與技能因子是核心構(gòu)成,但是根據(jù)需求的不同,可能會在不同的地方加入新的層級,以實現(xiàn)特殊的邏輯。

被動技能

被動技能同樣擁有技能因子,只是這里的因子更多承擔的是記錄邏輯的功能,而不存在觸發(fā)事件的問題。而值得記錄的,是這個模塊設(shè)計過程中逐步完善起的一整套完整的消息觸發(fā)機制,以下就做詳細記錄。

消息觸發(fā)機制

消息觸發(fā)的前提,是需要定義消息類型。也就是說,定義消息類型并在對應邏輯處埋點觸發(fā),這個核心思路是不變的。而變的是觸發(fā)過程中一系列的數(shù)據(jù)傳輸與判定流程。

現(xiàn)用表格,對這套消息觸發(fā)機制所用參數(shù)進行列舉與說明:

參數(shù)名 觸發(fā)時機 觸發(fā)關(guān)系 通用條件
字段類型 int int型數(shù)組 條件字符串
參數(shù)說明 具體的消息id 抽象概念,表示觸發(fā)消息的實體與被觸發(fā)內(nèi)容對應實體的關(guān)系集合 通用判定條件,即使用對應消息傳輸出來的數(shù)據(jù)進行自定義判斷,具體內(nèi)容交由策劃配置,程序僅實現(xiàn)通用條件邏輯

上表中,較為抽象的就是觸發(fā)關(guān)系的概念,這里作詳細說明:

觸發(fā)關(guān)系的本質(zhì)其實就是敵我關(guān)系枚舉。假設(shè)實體A所持有的技能監(jiān)聽消息1,當實體B觸發(fā)了消息1時,就需要判斷實體A與實體B的關(guān)系。若我們觸發(fā)關(guān)系填的是敵,表示敵對關(guān)系才觸發(fā),且A與B是敵對關(guān)系,那么實體A所持有的這個技能就會被觸發(fā)。

再舉例說明:有一個技能描述為“我方任意英雄得到buff時我要做某事”,那么這個技能的觸發(fā)時機就是“獲得buff”、觸發(fā)關(guān)系就是“我方”,這樣當我方任意一個英雄觸發(fā)了“獲取buff”的事件時,這個技能就滿足了觸發(fā)條件;若是敵方英雄觸發(fā)了“獲取buff”事件,因?qū)嶋H觸發(fā)關(guān)系是“敵”而不是“我”,則該技能不會被觸發(fā)。

上表中,通用條件內(nèi)有提到消息附帶的參數(shù),我們可以在設(shè)計階段就對每種事件設(shè)計獨立的參數(shù)。比如“獲取buff”這個消息,就可以將buff的創(chuàng)建者、獲得buff的實體、buff的id等具體信息,以約定的形式傳出,交由策劃通過配置表達式的形式生成條件語句。

這套流程除了用在被動技能外,針對戰(zhàn)場上的其他觸發(fā)邏輯同樣適用,因為當我們將整個戰(zhàn)場都實體化之后,都可以用相似的邏輯進行表達,而無需進行多余的邏輯判斷。

buff

buff模塊在邏輯部分被分為了兩部分:

  • buff實體
  • buff因子

buff作為一個實體,承載了它的生命周期內(nèi)所有的數(shù)據(jù),如生命周期、層數(shù)等數(shù)據(jù)。而buff需要執(zhí)行的邏輯,就放在了buff因子當中。

在這里需要重點說明buff因子,因為buff因子本身也具有不用的類型,這是由buff可以實現(xiàn)的功能決定的。

通常情況下,buff可以調(diào)用通用行為實現(xiàn)指定的效果,但是除此之外,我們又常常將buff用來作為光環(huán)效果、生命鏈等。所以,我們要對buff因子進行概念分類,以更好的協(xié)助我們對buff類型進行定義。

從概念上,buff因子分為如下幾種類型:

  1. 普通型:在指定的時間點執(zhí)行指定的邏輯,可以執(zhí)行一個通用行為,也可以執(zhí)行某個特殊buff所需要執(zhí)行的邏輯;
  2. 周期型:在buff存續(xù)期間做一個行為,等到buff消失時需要回收之前行為造成的后果,比如可以是添加一條屬性,等到buff消失時回收屬性;
  3. 回調(diào)型:回調(diào)型buff專門用于屬性計算后處理,即屬性輸出行為在計算好屬性變動值后,此類buff介入對該值進行二次處理,比如護盾buff就是在計算出扣血值后將該值作用于護盾值上,再將護盾值未能抵扣的傷害值返回,得到的就是最終的傷害值。

其中,回調(diào)型buff因子雖然只針對屬性輸出有效,但卻是我們戰(zhàn)斗中很多邏輯實現(xiàn)的必須手段。

子物體

子物體是通過行為創(chuàng)建的可在場上游走的實體的統(tǒng)稱。我們常見的子彈、陷阱等都可以通過子物體實現(xiàn)。

子物體也分為了兩部分:

  • 子物體本體
  • 子物體邏輯

從結(jié)構(gòu)上看,子物體與buff的結(jié)構(gòu)很像,但是二者最大的區(qū)別就在于子物體有自身的位置數(shù)據(jù)。僅此一點,子物體本體就多了一堆與位置與移動相關(guān)的配置。

再看子物體邏輯,它不像buff因子一般有那么多類型,子物體邏輯只負責在適當?shù)臅r機選擇目標執(zhí)行通用行為。所以,子物體邏輯需要配置在子物體不同的邏輯觸發(fā)時機上。

子物體的觸發(fā)時機分為:

  • 創(chuàng)建時
  • 實體碰撞進入時
  • 實體碰撞駛出時
  • 消失時

這是最基本的四種觸發(fā)時機,尤其碰撞的兩個時機,是基于子物體有位置與形狀屬性時,遍歷其他實體并與之判斷之后所產(chǎn)生的結(jié)果。

復雜業(yè)務邏輯與ECS嵌套開發(fā)的設(shè)計

圍繞復雜業(yè)務邏輯的開發(fā)與ECS模式的結(jié)合,歷來是很多人爭論的點,因為稍有不慎,開發(fā)方式就可能背離我們選取ECS模式的初衷,而使得項目開發(fā)復雜且效率低下。

我個人認為,即便選擇了ECS模式,我們?nèi)匀徊豢赡芡耆珤仐壝嫦驅(qū)ο蟮乃悸贰<寄?、buff這類實體,本身就需要很多的狀態(tài)數(shù)據(jù),使用面向?qū)ο蟮乃悸房梢愿玫奶幚頎顟B(tài)運行與切換的邏輯,完全面向過程反而會讓開發(fā)變得復雜。

在實體與組件層面,單一的一個技能、buff、子物體就是一個實體,依附他們的組件會根據(jù)需求有很多,但是核心組件(技能組件、buff組件、子物體組件)內(nèi)的邏輯部分,卻需要我們依據(jù)面向?qū)ο蟮乃悸啡ピO(shè)計一個實例。以我項目中的技能為例,CompSkill是一個技能組件,但是這個組件內(nèi)部卻有一個SkillLogic的實例,這個實例就是一個技能的完整結(jié)構(gòu),并擁有技能應當擁有的BeginEndTick方法。同時我所擁有的SkillSystem,則遍歷所有CompSkill,對其內(nèi)部的SkillLogic執(zhí)行心跳邏輯。

也許我現(xiàn)在所設(shè)想的結(jié)構(gòu)并不完美,還可以有更精煉的方式去實現(xiàn),但我依舊堅信,“教條主義”并不利于創(chuàng)新,也不利于實際生產(chǎn)。

此外,針對復雜邏輯的設(shè)計,也可以充分利用ECS的機制

子物體一節(jié)我有說,子物體本身可以有碰撞邏輯,但是這個碰撞邏輯基于擁有位置數(shù)據(jù)與形狀數(shù)據(jù)的子物體。所以在我的項目中,沒有碰撞邏輯的子物體本身不會有形狀數(shù)據(jù),這樣在子物體碰撞系統(tǒng)中便不會加載這些子物體去遍歷計算,從側(cè)面也省去了實體遍歷的性能開銷,而且對系統(tǒng)內(nèi)邏輯的理解也可以更為純粹。


以上就是我對這些年所做核心業(yè)務邏輯的一個整理,不見得適用所有項目,但應當有一些參考意義。

根據(jù)我實際開發(fā)的經(jīng)驗來看,邏輯的完全獨立對程序理解是件好事,但是對于策劃配置數(shù)據(jù)卻并不全然是,所以這個過程中也需要根據(jù)實際情況有適當?shù)恼{(diào)整。

下一篇,將開始記錄幀同步的內(nèi)容。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

推薦閱讀更多精彩內(nèi)容