最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

TomLooman_ActionRoguelike_第十四章帶有C++和更多框架擴展的UMG

2023-08-18 15:40 作者:別叫我小紅  | 我要投稿

該專欄用于保存對TomLooman的ActionRoguelike項目的學習筆記,學習過程中的思考與記錄不一定準確。


教程參考:https://github.com/tomlooman/ActionRoguelike

基于UE5.0的項目實現(xiàn):https://github.com/CarolBaggins2023/TomLooman_ActionRoguelike_Tutorial

2023_08_13

帶有C++和更多框架擴展的UMG:bot的血條,更多Heads-up Display(HUD),玩家角色的Spawn,控制臺命令

?

之前我們在藍圖中實現(xiàn)了角色的血條顯示,現(xiàn)在我們嘗試結(jié)合C++中實現(xiàn)bot受擊后,在bot身邊顯示血條的功能。

因為這類附著在某個對象上的UI,在很多類似的地方可以復用,比如可以用在寶箱或者血包上,所以我們對這種Widget創(chuàng)建一個基類,繼承自UserWidget。UserWidget是一個可以被我們擴展的Widget類。

我們創(chuàng)建的繼承自UserWidger的Widget類如下,

bot受擊產(chǎn)生UI后,這個UI應(yīng)該一直跟著bot移動,所以我們需要覆蓋Tick函數(shù),在其中確定UI的位置。在Widget中,承擔Tick功能的是NativeTick函數(shù)。

因為Widget的Tick函數(shù)不只確定UI的位置,還要執(zhí)行其它操作,所以要先調(diào)用父類函數(shù)。

我們知道UI在世界中的位置就是bot的Location,但是不知道世界中的位置與屏幕上的位置的轉(zhuǎn)換。這時就要調(diào)用UGameplayStatics::ProjectWorldToScreen函數(shù)確定UI在屏幕上的位置,其函數(shù)原型是bool UGameplayStatics::ProjectWorldToScreen(APlayerController const* Player, const FVector& WorldPosition, FVector2D& ScreenPosition, bool bPlayerViewportRelative)需要我們傳入玩家的Controller和UI在世界中的位置,并用一個二維向量的變量接收結(jié)果(UI在屏幕上的位置)。這里的AttachedActor是該類的成員變量,該變量在生成widget時被賦值。WorldOffset也是該類的成員變量,用來微調(diào)UI的位置。

但是ProjectWorldToScreen并不考慮UMG的DPI,所以這樣可能會讓最終生成位置產(chǎn)生偏移。所以我們要根據(jù)顯示窗口的DPI對UI顯示在屏幕上的位置進行調(diào)整。通過UWidgetLayoutLibrary::GetViewportScale可以獲得目前的DPI。

目前我們只確定了UI的位置,還要實際地渲染UI,所以我們調(diào)用SetRenderTranslation并傳入上面獲得的位置。然而我們原本并沒有一個實際地Widget來進行顯示,所以我們創(chuàng)建了一個SizeBox類型的成員變量,這個Widget之后會作為實際UI的父組件。

注意,這里的說明符meta = (BindWidget)表示這個成員變量會在Widget的設(shè)計圖中被綁定,綁定的對象是設(shè)計圖中與它類型和名稱相同的那個組件。

?

創(chuàng)建了上面的Widget基類后,我們可以在編輯器中創(chuàng)建它的藍圖子類,用來表示bot的血條。

因為我們在這個基類中有一個需要在Widget設(shè)計圖中綁定的SizeBox成員變量,所以剛創(chuàng)建完藍圖類后會出現(xiàn)報錯,提示SizeBox成員變量未綁定,此時我們就要創(chuàng)建一個SizeBox,并使它的名字與那個成員變量相同。

? ??

然后和玩家角色血條類似,我們用一個image表示血條,并使用之前用過的血條材料(該材料中的ProgressAlpha參數(shù)控制血條變化)。

?

現(xiàn)在bot受擊時還沒有真正地生成這個Widget,所以我們要對bot的OnHealthChanged成員函數(shù)進行補充。

我們不想重復生成UI,所以在bot類中聲明了一個之前定義的Widget基類的成員變量。

在OnHealthChanged中,我們先判斷是否已有UI。之后的邏輯和藍圖很像,用CreateWidget創(chuàng)建一個Widget,創(chuàng)建成功后AddToViewport。

注意在AddToViewport“之前“,我們要對Widget的AttachedActor進行賦值,因為AddToViewport會調(diào)用藍圖中的EventConstruct,而我們在EventConstruct的執(zhí)行流中需要訪問bot的屬性組件成員,所以必須要知道這個Widget到底Attach在哪個bot上。

CreateWidget第一個參數(shù)要求傳入OwnerT* OwningObject,這里我們選擇最簡單的GetWorld()(沒弄明白)。第二參數(shù)要求傳入生成的Widget的類別,類似于玩家角色射出的子彈的類別,這里我們將這個類別作為bot類的成員變量,并在編輯器中進行賦值。

?

現(xiàn)在我們攻擊bot后已經(jīng)會正確地在bot上顯示血條UI了,但是有兩個問題,(1)bot死亡被銷毀后崩潰,(2)血條不會變化。下面依次解決。

bot死亡被銷毀后崩潰的原因是,我們現(xiàn)在Widget的NativeTick中執(zhí)行了AttachedActor->GetActorLocation(),但并沒有檢查AttachedActor是否為空,所以一旦bot死亡被銷毀,AttachedActor就變成了空指針,再在它上面調(diào)用函數(shù)就會導致報錯。為了解決這個問題,我們在NativeTick中,先對AttachedActor進行空指針判斷,如果為空,則將Widget從父組件中移除,相當于刪除Widget。

血條不會變化是因為我們還沒有對血條材料中的ProgressAlpha進行賦值,而我們要先思考在哪里修改ProgressAlpha。因為血條形態(tài)的改變應(yīng)該在bot屬性組件中的Health發(fā)生改變時,所以就離不開屬性組件類中的委托。所以我們應(yīng)該將血條材料中的某個函數(shù)與擁有該血條的bot的屬性組件類中的委托綁定,當委托廣播時執(zhí)行該函數(shù),修改ProgressAlpha。

綁定過程如下,我們通過自定義的Widget基類中的AttachActor獲得擁有該血條的bot,并獲得該bot的屬性組件從,再接著完成自定義函數(shù)與委托的綁定。這里我們還在綁定后直接調(diào)用了委托,因為bot受傷這一事件發(fā)生在綁定之前,所以血條材料無法接受到bot的第一次委托廣播,也就是說如果不在綁定后立即執(zhí)行一次的話,bot第一次受擊就不會引起血條變化。

與委托綁定的事件與玩家角色的血條類似,不同的是,當bot血量小于等于0,也就是死亡時,我們延遲1s刪除血條UI。

?

?

我們玩家角色的血條、瞄準點等UI都是在藍圖中通過CreateWidget創(chuàng)建的,如果想這樣一個個創(chuàng)建,當Widget很多時會冗雜。所以我們想要將這些Widget集成到一個Widget中。

我們創(chuàng)建一個Widget將玩家角色的UI都放進去。我們還新建了兩個Text,一個表示玩家積分(現(xiàn)在還沒實現(xiàn)),一個表示游戲時間(后面講)。

在這些原本單獨的UI放進一個大的Widget中時,我們要對它們進行一些修改,主要的修改是去除原本的CanvasPanel,可以看到現(xiàn)在血條UI的外面已經(jīng)沒有表示顯示器區(qū)域的虛線方框了。另外此時UI的顯示方式我們可以選擇DesiredOnScreen,而不是默認的FullScreen,注意這只影響我們看到的,不影響UI實際放入大Widget后的樣子。

在大Widget中,我們將血條和積分豎直排列,這里不要手動對齊,而是要用VerticalBox,也就是它們外層顯示的虛線框。另外VerticalBox應(yīng)設(shè)置為SizeToContent,這樣它能隨里面Widget的大小調(diào)整自己的大小,而里面小Widget的大小可能通過它們自己的設(shè)計圖進行修改,大Widget中的引用也會相應(yīng)修改。

關(guān)于每個UI在屏幕上的位置,我們還可以通過UI的錨點(Anchor)與支點(Pivot)進行對齊(表述不準確,直接看例子)。例如下面的例子,我們想把游戲時間放在右上角,所以我們將UI的錨點(輻射狀的圖表)改到右上角,PositionX和PositionY是UI的錨點與支點之間的偏移,圖中表示支點在錨點的X軸向-50、Y軸向50,Alignment表示選擇哪個點作為UI的支點,候選點就是UI周圍方框上的八個小白點,左上是(0,0),右下是(1,1),上方中點是(0.5,0)。

?

在創(chuàng)建完這樣一個包含其他Widget的大Widget后,我們在玩家角色的藍圖中就不需要一個個地Create小Widget了,直接Create一個大Widgtet就可以了。

?

補充一下游戲時間UI的實現(xiàn)方法。

容易想到的是我們創(chuàng)建一個Text組件,然后將該Text與一個函數(shù)綁定。因為這個UI是顯示游戲時間的,時刻在改變,而不像攻擊傷害那樣,受傷了才出現(xiàn),所以可以不使用那種自定義事件的方法。

綁定的函數(shù)如下所示,之前我們也涉及過多次獲取游戲時間,例如受擊發(fā)光材料。我們可以通過UGameplayStatics::GetTimeSeconds獲得游戲時間,但是這個游戲時間是玩家本地的。當涉及到多人游戲時,這里會發(fā)生錯誤,比如游戲已經(jīng)開始了10分鐘,但是你剛加入游戲,此時如果你用UGameplayStatics::GetTimeSeconds獲取游戲時間,返回時間為0,因為游戲剛開始在玩家本地運行。但實際上我們希望獲取的是游戲真正運行的時間,這時我們可以使用GetServerWorldTimeSeconds,來獲取服務(wù)器上的游戲運行時間,而這個函數(shù)要從GameState中獲得。

另外,我們在游戲運行過程中的任何地方都不應(yīng)該使用FString,因為FString不會進行本地化,可能會出錯,所以這里把FString轉(zhuǎn)成了FText。

?

我們在最開始的時候通過把玩家角色和bot的藍圖子類拖拽到視口面板中,在世界中生成了玩家角色和bot?,F(xiàn)在我們已經(jīng)在自定義的GameMode中實現(xiàn)了bot的自動生成,所以視口面板中的bot可以刪除。那我們的玩家角色刪除后會怎么樣?我們會進入自由移動的上帝視角,沒有可以控制的角色。

我們可以在GameMode中設(shè)置游戲開始時玩家控制的Pawn的類型,

當我們在WorldSetting中用自定義的GameMode覆蓋了默認的GameMode后,我們在游戲開始后就可以控制一個PlayerCharacter類的Pawn。

但是現(xiàn)在開始游戲后我們依然沒有控制一個角色,因為角色不知道生成在哪里。這種情況下,我們可以在世界中放置一個PlayerStart,它將作為玩家角色的生成點。如果有多個PlayerStart,將會隨機挑選一個點生成玩家角色。

?

?

在游戲中,按下波浪號鍵可以呼出控制臺,我們可以調(diào)用控制臺中已有的一些函數(shù),也可以自定義控制臺命令。我們下面自定義兩個控制臺命令,玩家回血和殺死所有bot。

我們在ASCharacter中聲明并實現(xiàn)玩家回血函數(shù),函數(shù)的實現(xiàn)很好理解,重點在于函數(shù)宏中的說明符Exec,這表示我們可以在控制臺中調(diào)用該函數(shù)。

另外,這里我們第一次用了函數(shù)形參的默認值,UE中的函數(shù)形參默認值和C++規(guī)則相同。

我們在GameMode中實現(xiàn)殺死所有bot函數(shù),同樣要使用說明符Exec。這里我們在屬性組件類中添加了Kill函數(shù),其實就相當于受到滿生命值的傷害。

我們可以直接在游戲中呼出控制臺然后調(diào)用這些函數(shù)。

?

UE的CheatManager自帶一些控制臺函數(shù),例如void UCheatManager::God()

能將Pawn的CanBeDamaged設(shè)為false,也就是不受傷害(UE的Character自帶一些類似游戲中常見的屬性)。

但是調(diào)用這個函數(shù)并不會使我們的玩家角色不受傷害,因為我們的血量來自于我們自定義的屬性組件類,UE不知道。所以為了實現(xiàn)類似的效果,我們在屬性組件類的OnHealthChanged中增加一個功能,當Pawn的CanBeDamaged為false時,不繼續(xù)修改血量成員變量。

?

?

最后要注意,說明符Exec能讓類的成員函數(shù)在控制臺中調(diào)用,但僅限于以下的類:(1)PlayerController(2)玩家控制的Character(3)GameMode(4)CheatManager這種已經(jīng)有控制臺命令的類。

TomLooman_ActionRoguelike_第十四章帶有C++和更多框架擴展的UMG的評論 (共 條)

分享到微博請遵守國家法律
白山市| 祁门县| 平定县| 巫溪县| 冷水江市| 灵丘县| 阜南县| 嘉黎县| 抚州市| 莱州市| 竹溪县| 留坝县| 北碚区| 西畴县| 涿鹿县| 汤原县| 吴川市| 丹凤县| 紫阳县| 泌阳县| 周口市| 长岛县| 仁布县| 临朐县| 郯城县| 广丰县| 鄂温| 磴口县| 伊春市| 麻城市| 大连市| 林州市| 廊坊市| 华池县| 西华县| 墨江| 于都县| 利津县| 嘉义县| 通州市| 塔城市|