TomLooman_ActionRoguelike_AssignmentTwo
該專欄用于保存對TomLooman的ActionRoguelike項(xiàng)目的學(xué)習(xí)筆記,學(xué)習(xí)過程中的思考與記錄不一定準(zhǔn)確。
教程參考:https://github.com/tomlooman/ActionRoguelike
基于UE5.0的項(xiàng)目實(shí)現(xiàn):https://github.com/CarolBaggins2023/TomLooman_ActionRoguelike_Tutorial

2023_08_03
Assignment Two:校正子彈方向,黑洞子彈,奧術(shù)躍遷子彈
?
在目前的實(shí)現(xiàn)中,Projectile在Spawn時(shí)的Rotation和Controller的Rotation相同。

因?yàn)樵贑haracter的構(gòu)造函數(shù)中,我們用Controller的Rotation控制SpringArm組件,又因?yàn)镃amera附著到SpringArm上,所以玩家看到的視野受控制器(鼠標(biāo)等)控制。(因此下面用Camera的朝向就是Controller的朝向)

在Camera的位置改變前,從Camera進(jìn)行LineTrace和從Character進(jìn)行LineTrace,兩條Line在俯視角下是重合的,因此Projectile的落點(diǎn)與屏幕中心點(diǎn)只會(huì)有z軸上的偏差(不考慮Projectile從Character的手部Spawn,而是從Character的中心Spawn)。
在Camera的位置改變后,從Camera進(jìn)行LineTrace和從Character進(jìn)行LineTrace,兩條Line在俯視角下存在偏移,因此如果玩家瞄準(zhǔn)面前豎直平面的中心進(jìn)行Attack,則Projectile最終的落點(diǎn)會(huì)在左半平面。

?
為了解決上述問題,我們需要尋找一個(gè)新的Rotator來組成子彈Spawn時(shí)的Transform。
按照作業(yè)提示給出的邏輯,這個(gè)Rotator應(yīng)該使子彈轉(zhuǎn)向落點(diǎn)。這里的落點(diǎn)指的就是從Camera射出的射線(實(shí)現(xiàn)時(shí)用長線段)與游戲場景中物體的交點(diǎn),如果沒有交點(diǎn),則落點(diǎn)就是長線段的端點(diǎn)。于是,已知起點(diǎn)(角色手部)和終點(diǎn)(上述落點(diǎn)),我們能夠表示一個(gè)Rotator。
從世界坐標(biāo)原點(diǎn)到子彈Spawn的起點(diǎn)和終點(diǎn),可以做出兩個(gè)方向向量分別記為Start和End,上述要求相當(dāng)于求一個(gè)Rotator讓Start轉(zhuǎn)向End,那么這個(gè)Rotator對應(yīng)的方向向量就是End-Start。
Vector相減還是Vector,不是Rotator,但我們可以先把Vector轉(zhuǎn)換為RotationMatrix(旋轉(zhuǎn)矩陣),再從RotationMatrix轉(zhuǎn)成Rotator。
實(shí)現(xiàn)如下,
先求Start,和原本的求法一致。需要注意的是,向量和點(diǎn)可以認(rèn)為是等同的。

然后求End,通過以上分析,End可以自然地通過LineTrace求得。
LineTrace的起點(diǎn)是Camera的位置,注意不是Character的眼部。LineTrace的終點(diǎn)是從LineTrace起點(diǎn)出發(fā),沿著控制器Rotation的長線段(這里涉及了Rotator和Vector的轉(zhuǎn)換)。注意,由于Camera綁定在SpringArm上,而SpringArm使用控制器Rotation控制轉(zhuǎn)向,所以這里的ControlRotation相當(dāng)于是Camera的Rotation。

然后是設(shè)置LineTrace中的一些參數(shù),包括要檢測什么類型的對象,碰撞形狀是什么(射線橫截面形狀),以及要IgnoreCharacter自己。

然后通過SweepSingleByObjectType,我們可以得到Hit(包含碰撞到的對象的信息,因?yàn)檫@里是Single而不是Multi,所以只有一個(gè)對象,Hit也不是數(shù)組)。根據(jù)是否有碰撞到物體,決定End是確實(shí)存在的碰撞點(diǎn),還是LineTrace的終點(diǎn)。


根據(jù)上述方法得到的子彈Spawn的位置Start和最終的位置End,我們計(jì)算出旋轉(zhuǎn)對應(yīng)的方向方向,再由方向向量轉(zhuǎn)為旋轉(zhuǎn)矩陣,由旋轉(zhuǎn)矩陣轉(zhuǎn)為Rotator。最終得到正確的子彈Spawn時(shí)的Transform。

?
?
?
UPROPERTY的Specifier中的Edit/VisibleDefaultsOnly和Edit/VisibleInstanceOnly的區(qū)別,關(guān)鍵在于區(qū)別Defaults和Instance??梢园袲efaults理解為類,Instance理解為類的實(shí)例。因此,DefaultsOnly是只能對整個(gè)類修改,不能只改某個(gè)實(shí)例;InstanceOnly是只能改某個(gè)實(shí)例,不能改整個(gè)類。
當(dāng)我們進(jìn)入藍(lán)圖界面時(shí),相當(dāng)于針對整個(gè)類修改或?yàn)g覽,此時(shí)只能看到DefaultsOnly的成員變量。

當(dāng)我們在關(guān)卡編輯器中選中某個(gè)實(shí)例時(shí),相當(dāng)于針對某個(gè)實(shí)例修改或?yàn)g覽,此時(shí)只能看到InstanceOnly的成員變量。

?
?
我們對子彈類的進(jìn)行了重構(gòu)。
首先是創(chuàng)建了Projectile的基類class ACTIONROGUELIKE_API ASProjectileBase : public AActor。
在基類中我們聲明了子彈通用的成員變量,碰撞組件、粒子效果組件、移動(dòng)組件和爆炸的粒子效果,并在基類構(gòu)造函數(shù)中進(jìn)行了默認(rèn)的初始化,默認(rèn)初始化類似于原本的MagicProjectile,各派生類可后續(xù)進(jìn)行覆蓋和拓展。


我們還聲明了子彈類通用的成員函數(shù),主要包括Hit反應(yīng)和爆炸,它們的實(shí)現(xiàn)類似于爆炸桶。


目前我們將ignore instigator的邏輯寫在BeginPlay中,

?
然后,在有了ProjectileBase這樣的基類后,我們對原本的MagicProjectile進(jìn)行修改。
我們刪去了成員變量的重復(fù)聲明和在構(gòu)造函數(shù)中的初始化。


需要注意的是,當(dāng)涉及繼承關(guān)系時(shí),對于覆蓋的虛函數(shù),要考慮是否執(zhí)行父類的函數(shù)實(shí)現(xiàn)。比如對于繼承ProjectileBase的MagicProjectile,在BeginPlay中應(yīng)該執(zhí)行父類的BeginPlay,否則無法進(jìn)行ignore instigator(在派生類的成員函數(shù)中再寫一遍當(dāng)然是可以的)。

?
我們創(chuàng)建了BlackholeProjectile,能吸附彈道上的物體,一定時(shí)間后自我銷毀。它同樣繼承ProjectileBase,但為了實(shí)現(xiàn)吸附物體的效果,增加了徑向力組件成員變量

徑向力組件中有Impulse(沖量)和Force(力)兩種可以影響范圍內(nèi)物體的方式。其中,Impulse直接給物體疊加新的速度,物體速度會(huì)馬上變化,而Force給物體施加力,物體速度的變化存在一個(gè)過程。

因?yàn)槲覀儾幌M诙醋訌棔?huì)因?yàn)榕鲎捕V挂苿?dòng),所以我們將碰撞的所有通道都設(shè)為overlap。而且我們也不想控制的Character受徑向力的影響,所以將Pawn從ObjectTypeToEffect中去除。這些操作在構(gòu)造函數(shù)中完成。(因?yàn)榕缮悩?gòu)造函數(shù)先調(diào)用基類構(gòu)造函數(shù),所以構(gòu)造函數(shù)中不存在用Super::來顯式地調(diào)用構(gòu)造函數(shù))

自我銷毀的實(shí)現(xiàn)和子彈延時(shí)射擊配合動(dòng)畫的實(shí)現(xiàn)類似。

最終效果如下,黑洞會(huì)在5秒后消失。

?
我們還實(shí)現(xiàn)了傳送子彈,它的邏輯是,Character發(fā)射子彈,子彈在DetonateDelay(引爆延時(shí))后會(huì)自我引爆,或者發(fā)生Hit被引爆,引爆后再經(jīng)過TeleportDelay后Character會(huì)被傳送到子彈引爆的位置。
所以,傳送子彈有兩個(gè)額外的成員變量表示兩個(gè)延時(shí)。

因?yàn)镈etonateDelay在子彈射出時(shí)就開始計(jì)時(shí),所以在BeginPlay中就布置計(jì)時(shí)器。

注意,這里使用的函數(shù)指針是Explode而不是Explode_Implementation,原因之前有討論(因?yàn)開Implementation只是C++中的實(shí)現(xiàn),不包括藍(lán)圖實(shí)現(xiàn))。
在爆炸的實(shí)現(xiàn)中,我們需要表現(xiàn)爆炸的粒子效果、停止子彈飛行的粒子效果、停止子彈運(yùn)動(dòng)、設(shè)置Character的傳送計(jì)時(shí)器。
需要注意的是,我們在一開始清空了引爆的計(jì)時(shí)器。這是因?yàn)槟苡|發(fā)Explode的不只是Beginplay中的計(jì)時(shí)器,還有ProjectileBase中的OnActorHit。所以當(dāng)子彈發(fā)生碰撞時(shí),也會(huì)發(fā)生Explode,從而觸發(fā)傳送效果。如果不清空DetonateDelay計(jì)時(shí)器,那么當(dāng)子彈發(fā)生碰撞時(shí),會(huì)觸發(fā)兩次Explode,第一次是觸發(fā)ProjectileBase的OnActorHit中的Explode,第二次是觸發(fā)計(jì)時(shí)器設(shè)置的Explode。

我們用TeleportTo實(shí)現(xiàn)傳送,傳送的Location為子彈Explode的位置,Rotation為Character自身的Rotation。

注意:使用TeleportTo實(shí)現(xiàn)傳送,當(dāng)子彈在地面Explode時(shí)會(huì)失敗。這可能是由于UE4中地面的性質(zhì)和UE5中地面的性質(zhì)不同所導(dǎo)致的。
因?yàn)門eleportTo會(huì)檢測待傳送的Actor是否能fit到傳送的位置,并以bool值的形式返回。所以我們通過觀察TeleportTo的返回值得到了以上的推測。

當(dāng)子彈在Cube上Explode時(shí),

當(dāng)子彈在地面上Explode時(shí),

為了避免這種情況,我們可以直接使用SetActorLocation,因?yàn)門eleportTo實(shí)際上也是調(diào)用SetActorLocation實(shí)現(xiàn)的,又在此之上增加了Actor是否fit傳送地點(diǎn)的判定。
