本項(xiàng)目同步上傳于github和coding上,國(guó)內(nèi)讀者可以通過(guò)在coding下載項(xiàng)目,注意本套開(kāi)發(fā)日志同步的是Tutorial分支,其他分支內(nèi)容可能與日志內(nèi)容不符。請(qǐng)注意
github地址:https://github.com/Liweimin0512/uRPG
coding地址:https://git.dev.tencent.com/JeremyBrett/uRPG.git
今天我們來(lái)搞定角色的生命值和魔法值,在此之前我們要解釋一個(gè)重要的概念——組件。
基于組件的游戲開(kāi)發(fā)
也許你聽(tīng)過(guò)“組合大于繼承”(沒(méi)聽(tīng)過(guò)就先去聽(tīng)一下),簡(jiǎn)單來(lái)講就是在游戲開(kāi)發(fā)中描述一個(gè)游戲物體有什么比描述它是什么更合理。
那么這里我想把生命值和魔法值做成組件,因?yàn)橛螒蛑谐送婕医巧螒蛑械腘PC、怪物甚至是一些“可交互物”都是有生命值的概念的。
起先,我想將生命值、魔法值作為兩個(gè)組件。但更深入的分析,我將這兩個(gè)數(shù)值抽象成一種叫做“狀態(tài)”的組件,這個(gè)組件本身負(fù)責(zé)維護(hù)角色的各種狀態(tài)數(shù)值,比如這里講的生命值和魔法值。如果我們要開(kāi)發(fā)一款類《饑荒》游戲,則這里的狀態(tài)就可以是生命值、饑餓值和san值。
閑話少說(shuō),我們開(kāi)始操作。
準(zhǔn)備工作
首先我們先新建一個(gè)枚舉藍(lán)圖,取名為E_Stats,有三個(gè)值,分別如下圖所示:
然后我們創(chuàng)建一個(gè)結(jié)構(gòu)體,保存最大、最小值、當(dāng)前值和回復(fù)時(shí)間、恢復(fù)速度等,取名為s_StateData。如下圖所示:
組件:BpC_StateManager
然后我們創(chuàng)建一個(gè)組件,也就是今天的主角:BpC_StateManager。這個(gè)組件負(fù)責(zé)儲(chǔ)存、管理其所在Actor上的所有State,并在需要的時(shí)候通過(guò)事件調(diào)度器完成回調(diào),這個(gè)我們會(huì)在之后講到。
正如上文所說(shuō),我們需要儲(chǔ)存狀態(tài),所以這里我們新建一個(gè)Map類型的變量,取名為StatDatas:
這個(gè)組件需要幾個(gè)函數(shù),我們一個(gè)一個(gè)新建
首先是GetStateData,很簡(jiǎn)單,獲取狀態(tài)數(shù)據(jù):
其次就是設(shè)置狀態(tài)數(shù)據(jù),SetStateData:
這里調(diào)用了UpdateStat這個(gè)事件調(diào)度器,這個(gè)我們?cè)谥笾v。此外,為了方便設(shè)置當(dāng)前值,這里還寫(xiě)了一個(gè)函數(shù)ModifyStat:
這個(gè)函數(shù)需要傳入狀態(tài)類型、更改的值。先將這兩個(gè)值作為局部變量存儲(chǔ)起來(lái)(主要為了代碼的整潔)。然后判斷當(dāng)前Animated?是否為真。為真則直接返回。否則:
通過(guò)SetStatData設(shè)置狀態(tài)數(shù)據(jù),設(shè)置的方式就是先獲取當(dāng)前值,然后加上傳入的值,并且保證在最大、最小值之間(Clamp)。
部分狀態(tài)可能需要自動(dòng)回復(fù),這部分我的做法是創(chuàng)建一個(gè)自定義事件,Actor調(diào)用這個(gè)事件,就會(huì)通過(guò)SetTimerByFuncitonName節(jié)點(diǎn)來(lái)周期性調(diào)用狀態(tài)恢復(fù)函數(shù),具體代碼如下圖所示:
首先便利StatDatas,其中的項(xiàng)。先判斷RateValue和RegenValue都不為零。然后:
RateValue就是SetTimerByFuncitonName調(diào)用的時(shí)間,具體的函數(shù)名依據(jù)StatType確認(rèn),然后調(diào)用上文中編寫(xiě)的SetStatData函數(shù)即可。
Actor
首先就是給Bp_CharacterBase這個(gè)Actor添加BpC_StateManager組件,并設(shè)置組件的值如下圖所示:
如果你跟著一步步操作到現(xiàn)在,應(yīng)該已經(jīng)意識(shí)到StatType中“/”的作用,這里就不再多說(shuō)了。
還記得上文中所說(shuō)的事件調(diào)度器么?這里我們要在Bp_CharacterBase中調(diào)用它。就在BeginPlay事件中,添加如下節(jié)點(diǎn):
上圖代碼的含義,就是其BpC_StateManager組件調(diào)用UpdateStat狀態(tài)時(shí)候,也會(huì)調(diào)用下方的SetupStaBar函數(shù),這里之所以如此實(shí)現(xiàn),是因?yàn)椴煌腁ctor對(duì)于UpdateState事件的處理方式是不同的,比如主角是要顯示在主界面UI上,而怪物則是顯示在頭頂版血條上。
并且需要注意的就是,在最后調(diào)用BpC_StateManager的SetupStatRegeneration事件。
現(xiàn)在我們需要讓狀態(tài)變化的結(jié)果顯示在UI上,創(chuàng)建一個(gè)Weiget,并取名W_Main。添加兩個(gè)ProgressBar。就可以關(guān)掉了。
在Bp_CharacterBase中創(chuàng)建W_Main并添加到ViewPort上:
雖然UE4為我們提供了關(guān)于ProgessBar中很多參數(shù)很方便的綁定,但這并不是我們學(xué)習(xí)的重點(diǎn)。仔細(xì)思考發(fā)現(xiàn),我們并不需要每幀檢測(cè)State數(shù)值的變化,而只需要在UpdateState時(shí)候更改ProgessBar上的顯示即可,所以函數(shù)SetupStatBar的實(shí)現(xiàn)如下圖所示:
Debug
又到了愉快的Debug時(shí)間,這次的Debug方式依然簡(jiǎn)單,在Bp_CharacterBase的事件圖表中添加如下事件:
然后運(yùn)行游戲,試著改變Mana,然后將StatType修改為Health試試。好的,搞定,完美!
下集預(yù)告
之后的幾期,我們將陸續(xù)開(kāi)發(fā)一個(gè)可擴(kuò)展的技能系統(tǒng)。這個(gè)技能系統(tǒng)盡可能地由數(shù)據(jù)驅(qū)動(dòng),但我并不會(huì)直接創(chuàng)造一個(gè)這樣的系統(tǒng),而是由最“笨”的方法不斷改進(jìn)、重構(gòu),最終得到的,這個(gè)過(guò)程也將記錄下來(lái),可以更好地和同學(xué)們分享。那我們下期再見(jiàn)!