UE4的移動碰撞
好奇的同學可能會問,既然有了PhysX物理引擎,為什麼不直接用它來完成角色移動呢?
原因有很多,這裡列舉幾個比較典型的
- 討厭的tunneling effect,用過物理引擎的同學可能會遇到,如果角色的移動速度過快,它很可能會穿透牆。所以,角色的最大速度往往是被限制的,這並不能滿足遊戲設計需求。即使不出現tunnel effect,角色碰到了牆角,會出現抽搐抖動,甚至移動到不可知的位置。
- 不能直接控制角色,想讓物理引擎的剛體移動,需要施加impluse或者force推力,這並不能讓角色移動到想要到的位置。
- 摩擦力問題,想讓角色站在斜坡上,需要設定無限大的摩擦力,但這回導致角色無法在斜坡上移動。
- 不受控制的跳躍,在類似波浪線有起有伏的地形上移動,不可避免的會騰空。
以上這些情況如果使用物理引擎幾乎是無法避免的,所以目前幾乎所有的遊戲都會自定義自己的移動模組,模組的複雜程度根據遊戲的型別規模有著天壤之別,運動類遊戲和第一人稱射擊遊戲的移動模組往往是最複雜的。而第一人稱射擊類遊戲的移動模組更具有通用性,經過多年發展,已經比較成熟,所以本文參考UE4中的程式碼,抽取其中核心邏輯,向大家介紹collide and slide演算法。
在瞭解UE4的移動邏輯之前,我們先熟悉下碰撞的基礎介面
UE4移動中碰撞檢測主要使用PhysX的Geometry Queries(幾何查詢)功能
- 射線檢測RayCasts
- 重疊檢測Overlaps
- 滲透深度計算Penetration Depth
- Sweeps檢測
- InitialOverlaps檢測
UE4把查詢後返回的hit封裝成了FHitResult
FHitResult的結構如下
bBlockingHit是否發生碰撞
bStartPenetrating是否在檢測開始就有滲透情況
Time碰撞後實際移動距離除以檢測移動距離
Distance碰撞後實際移動距離
Location碰撞後最終位置
ImpactPoint碰撞接觸點
Normal碰撞切面法向量
ImpactNormal碰撞切面法向量(非膠囊體和球體檢測與Normal不同)
TraceStart檢測開始位置
TraceEnd檢測結束位置
PenetrationDepth滲透深度
我們可以藉助以下兩種移動中常見的情況熟悉一下這些引數,
第一種是常見的膠囊體Sweep查詢
查詢開始結束分別是TraceStart和TraceEnd兩個位置,如果碰到了障礙,bBlockingHit就是true,膠囊體最終會停在Location位置,它移動的距離是Distance,Time是一個0到1的值,表示實際移動距離比查詢距離。還有一些可能會用的引數,比如碰撞接觸點ImpacePoint,碰撞切面法向量Normal和ImpactNormal
第二種常見的情況通常是InitialOverlaps,開始位置檢測到了重疊
這時候bStartPenetrating是true,通過滲透深度計算可以獲得PenetrationDepth,這個引數對於處理移動中穿透的情況非常重要
仔細觀察的話可以發現上面膠囊體的Sweep就是一次簡單的移動過程,UE4將這個過程進一步封裝成了SafeMoveUpdatedComponent,它是UE4移動最關鍵的函式,幾乎所有的移動都要靠它來完成。它的主要功能有以下幾點
- 篩選Hit
- SetLocation並遞迴更新子元件
- UpdateOverlap,Overlap檢測
- 解決滲透的情況,bStartPenetration
- 返回檢測結果Hit
下面分別介紹一下這些功能,注意下面的符號▽△用於表示函式的開始和結束
SafeMoveUpdatedComponent
UPrimitiveComponent::MoveComponentImpl
呼叫SweepMulti獲取合理的Hit
呼叫SweepMulti得到的所有Hit需要拉回微小的距離(縮小hit.time),避免因為浮點數精度的問題導致跟碰撞物重疊
如果檢測到多個block hit,優先選擇不是在初始位置就檢測到block的hit,否則的話選取跟運動方向最相反的hit
如上圖是俯檢視,圓形是膠囊體,方形是碰撞物,紅色箭頭是運動方向,膠囊體同時跟3個障礙物發生的碰撞,得到了3個hit,也就是圖中的3個綠色剪頭,按照篩選規則,選取跟紅色箭頭方向最相反的,也就是中間的綠色箭頭的hit。
SetPosition以及相關操作
- 呼叫SetWorldLocationAndRotation
- 更新ComponentToWorld Transform矩陣
- 更新父元件和遞迴更新子元件
- 更新導航網格資料,Bounds邊界
- 更新RenderTransform以及PhysicsTransform
呼叫UpdateOverlap更新重疊狀態
- 呼叫OverlapMulti,獲得檢測結果
- 更新Overlap Components列表,刪除不再Overlap的Component,新增新的Component。
- 更新子Component的
Overlap Components列表
- 更新PhysicsVolume(比如進入離開水域)
UPrimitiveComponent::MoveComponentImpl
如果呼叫MoveComponentImpl返回的hit結果bStartPenetrating是true,需要呼叫ResolvePenetration解決穿透的問題
ResolvePenetration
上圖是俯檢視,圓形代表膠囊體,方形是障礙物,膠囊體跟左邊的障礙物穿透了,比較直觀的解決方法是將它按照左邊重疊的綠色箭頭拉回,拉回的距離就是上面提到的PenetrationDepth變數,如果拉回過程中又跟右邊的障礙物穿透了,這時候會得到右邊的綠色箭頭,左右兩邊的箭頭疊加,也就是向量相加,會得到中間向下的箭頭,按著這個方向拉回,就會避免穿透問題。如果調整位置成功了,還需要再次嘗試最開始的移動。
ResolvePenetration
SafeMoveUpdatedComponent
SafeMoveUpdateComponent可以看做是底層碰撞檢測和上層移動邏輯的中間層,是基礎的移動單元,接下來我們要介紹的移動邏輯,看似複雜,其實都是由這些移動單元構成的。整個移動邏輯的主函式是PerformMovement,我們還是按照函式的呼叫順序梳理一遍它的主要邏輯。
PerformMovement
1.根據輸入向量InputVector計算加速度向量Acceleration
2.隨著被騎乘物MovementBase(比如電梯,載具)移動
3.將衝力Impulse和推力Force作用於速度Velocity,一般用於擊退和徑向運動
4.根據不同的運動狀態運動
- MOVE_None(不做運動)
- MOVE_Walking(踩地面上運動)
- MOVE_NavWalking(踩導航網格上運動)
- MOVE_Falling(在空中受重力加速度)
- MOVE_Flying(不受重力加速度的運動)
- MOVE_Swiming(在水中運動)
- MOVE_Custom(自定義運動,比如插值運動)
先看下MOVE_Walking
PhysWalking
首先將速度和加速度的垂直方向分量設為0,方向始終保持在水平面上
CalcVelocity
1.計算速度,先設定為RequestedVelocity(尋路元件PathFollowingComponent根據路徑不斷設定該速度)
2.加速度是0的時候,將受到減速度BrakingDeceleration和摩擦力的影響而減速
3.加速度不是0的時候,摩擦力將會影響速度方向改變快慢
4.計算速度向量Velocity+=Acceleration*DeltaTime
5.最後,如果支援RVOAvoidance,將會根據RVO重新計算速度,避免跟其他角色重疊在一起,效果就像被彈回來。
CalcVelocity
MoveAlongFloor
計算移動向量Delta=Velocity*DeltaTime
根據地面坡度調整移動向量方向,如上圖需要改為沿著面1坡度的方向,也就是紅色箭頭的方向,呼叫SafeMoveUpdatedComponent
如果返回Hit結果是block,如上圖碰到了面2,通過返回的Hit的Normal引數檢測到面2的斜面坡度較緩,這時可以將剩下的移動向量改為沿著面2移動,再次呼叫SafeMoveUpdatedComponent,如果返回的Hit結果還是block或者面2非常陡峭(如下圖所示),可以開始嘗試呼叫StepUp上樓的邏輯
StepUp
理想情況下的上樓梯過程如圖所示,它是由3次移動構成,首先向上移動MaxStepHeight高度,然後向前移動(向前移動過程中如果檢測到block,需要呼叫SlideAlongSurface),最後向下移動,落到面2上面。當然,存在很多情況會導致StepUp失敗,比如移動過程中檢測到穿透Penetration,最終無法落到一個合理的落腳點(比如面2比較陡峭),都會導致呼叫StepUp失敗,在這種情況下,我們需要呼叫SlideAlongSurface,貼著面走
StepUp
在呼叫SlideAlongSurface貼著面走之前,需要呼叫HandleImpact,處理碰撞發生後帶來的副作用
HandleImpact
傳送MoveBlockedBy事件,如果開啟bEnablePhysicsInteraction,可以給與剛體一個反推力
HandleImpact
SlideAlongSurface
二維的圖示並不能很好表示貼牆走的情況,我們看下上面這個截圖,紅色箭頭表示最開始移動方向,撞到面2後,我們呼叫StepUp失敗,嘗試SlideAlongSurface,於是移動方向變為貼著面2的黃色箭頭,如果按照黃色箭頭的移動過程中很不幸又碰到了一個面,我們需要呼叫TwoWallAdjust
TwoWallAdjust
利用兩個面法向量計算面2和麵3的夾角,如果夾角大於90度,我們可以將移動方向變為沿著面3的綠色箭頭
如果面2和麵3的夾角小於90度,我們可以沿著面2和麵3的夾縫(如下圖的綠色向量)繼續移動,這個夾縫向量可以通過面2和麵3的法向量的叉乘結果計算出來,當然這個夾縫向量的傾斜角度不能過於陡峭,否則角色也是不能按照這個方向移動的。
TwoWallAdjust
SlideAlongSurface
MoveAlongFloor
到這裡MoveAlongFloor就執行完了,然後還需要呼叫FindFloor,檢測地面,調整縱座標,保證角色始終貼著地表
FindFloor
FindFloor返回的結果也是個比較重要的結構,我們看下它的引數
FFindFloorResult
bBlockHit是否跟地面有碰撞
bWalkableFloor可以行走的地面
bLineTrace是否是通過line trace檢測出來的結果
FloorDist Sweep查詢到地面的距離
LineDist LineTrace查詢到地面的距離
HitResult跟地面的FHitResult
ComputeFloorDist
一般情況下,比如下圖中的情況,我們只需要一次垂直向下Sweep檢測就可以計算出FloorDist,注意檢測的距離是之前StepUp向上檢測的距離。這時候FloorDist等於返回Hit的Distance
如果返回的的Hit是bStartPenetration是true話則需要用一個縮小的膠囊體來重新向下Sweep,算出來的FloorDist減去縮水的高度就是原膠囊體跟地面的距離
如果用縮小膠囊體Sweep還是有穿透情況,這時候需要改用line trace,從膠囊體的中心向下trace膠囊體的半個身高,如果檢測到了hit,則可以計算出陷入到地面以下的高度
注意無論是sweep還是line
trace設定膠囊體向上抬的調整高度MaxPenetrationAdjust最大隻能是膠囊體的半徑,如果陷入地下的深度大於調整高度,一次調整是無法將膠囊體從地面抬出來的,往往需要多幀處理才可以。
ComputeFloorDist
FindFloor
通過呼叫AdjustFloorHeight根據之前計算的FloorDist來調整角色的垂直座標
如果FindFloorResult的bWalkableFloor是false,需要呼叫CheckFall,切換成MOVE_Falling狀態
PhysWalking
其他幾種運動狀態這裡不做具體說明,大體邏輯是基本相似的,區別在於計算速度和對返回Hit的特殊處理上。
Physx也提供了CharacterController移動庫,有興趣的可以參考下。
作者:李雪峰
專欄地址:https://zhuanlan.zhihu.com/p/33529865
相關文章
- UE4 C++(11):移動元件和碰撞C++元件
- 《Exploring in UE4》遊戲角色的移動原理(上)遊戲
- 《Exploring in UE4》遊戲角色的移動原理(下)遊戲
- 《Exploring in UE4》移動元件詳解[原理分析]元件
- 如何在 UE4 移動端中實現 HZB?
- ue4繫結動畫、重定向動畫動畫
- UE4 ProjectileMovement Component 延遲啟動Project
- (UE4 4.20)UE4 TimeLine(UTimelineComponent)
- (UE4 4.20)UE4 UCLASS,UENUM, USTRUCT, UPROPERTY 的 常用配置Struct
- 科技互動沙盤實現傳統與科技的碰撞
- 原生js實現一個DIV的碰撞反彈運動JS
- (UE4 4.20 )UE4的GC(垃圾回收)程式設計規範GC程式設計
- ccf碰撞的小球(100分)
- Html5 Canvas動畫基礎(碰撞檢測)HTMLCanvas動畫
- 運動與資料的碰撞,華為分析運動健康行業模板上線行業
- 雜湊碰撞
- 碰撞檢測
- 移動的“豹變”
- UE4 如何關閉自動更新導航,手動更新導航
- RFID的防碰撞是什麼
- ORA,全球文化與科技的碰撞
- MD5碰撞的演化之路
- UE4委託
- UE4 c++ -- 簡單的UMGC++
- 移動端的判斷
- 移動端的那些坑
- (UE4 4.20)UE4 繼承AnimNotify建立自定義動畫通知事件(結合PoseableMeshComponent實現技能殘影效果)繼承動畫事件
- python如何檢測pygame中的碰撞PythonGAM
- 策略模式、策略模式與Spring的碰撞模式Spring
- 公有云和開源的商業碰撞
- UE4 智慧指標指標
- 《Inside UE4》開篇IDE
- 橫版動作經典遊戲IP與肉鴿戰棋的全新碰撞遊戲
- iterator移動
- 棋子移動
- UE4 C++ Widget的NativeConstruct 與 NativePreConstructC++Struct
- 密碼學系列之:碰撞抵禦和碰撞攻擊collision attack密碼學
- 移動端中的陀螺儀