UE4.25 中有各种Trace,最常用的就是 LIneTrace,即发射线看打到谁。
一、World.h 中的 Trace
在引擎 World.h 中,
- Trace 类型有两种:LineTrace、SweepTrace(射线方式、扫掠方式);
- Trace 数量有两种: TraceSingle、TraceMulti(也就是射到一个就停止,还是可以射穿多个);
- Trace 方式有三种:ByChannel、ByObjectType、ByProfile(通过不同方式选择哪些东西可以Trace)。
下边这个函数就是 “射线方式、射一个停止、通过 Channel查找射到谁” 的一个 LineTrace 方法。
/**
* Trace a ray against the world using a specific channel and return the first blocking hit
* @param OutHit First blocking hit found
* @param Start Start location of the ray
* @param End End location of the ray
* @param TraceChannel The 'channel' that this ray is in, used to determine which components to hit
* @param Params Additional parameters used for the trace
* @param ResponseParam ResponseContainer to be used for this trace
* @return TRUE if a blocking hit is found
*/
bool LineTraceSingleByChannel(struct FHitResult& OutHit,const FVector& Start,const FVector& End,ECollisionChannel TraceChannel,const FCollisionQueryParams& Params = FCollisionQueryParams::DefaultQueryParam, const FCollisionResponseParams& ResponseParam = FCollisionResponseParams::DefaultResponseParam) const;
重点分析上边第三条:Trace 的方式。
1. ByChannel
这个名称比较迷惑,通过 Channel 来控制那些东西可以 Trace,比如想只 Trace WorldStatic 的 Object,但其实 TraceChannel
参数不是 “只与 WorldStatic 交互” 的意思,而是 The 'channel' that this ray is in
(源码注释),也就是这根射线的 Collision Type 是 WorldStatic,所有与 WorldStatic 类型有Block的,都会挡住射线,就会出现想发出一根只与 WorldStatic Object 交互的射线,但是却被怪物挡住的情况!
上边这种需求应该用下边这种 ByObjectType 的 LineTrace 方法。
2. ByObjectType
如果想发出射线,与碰到场景(一般地面、不动的物体等场景物体都是 WorldStatic)就停止,可以用
FHitResult HitResult;
GetWorld()->LineTraceSingleByObjectType(HitResult, Location, FVector(Location.X, Location.Y, 0), ECC_WorldStatic);
这样的射线,只在碰到 WorldStatic Object 时停止,会穿过怪物、人物等 Dynamic物体。上边这个函数就可以从 Location 位置向世界坐标系的 Z 轴负方向(一般就是朝下)发射线,遇到地面等静态物体停止,再通过 Location.Z - HitResult.Location.Z
就可以算出 Location 位置距离地面的高度了。
3. ByProfile
看参数和注释应该是通过 FName ProfileName
确定射线会射到的物体,还没用过,用到再说~
二、UKismetSystemLibrary 中的 Trace
UKismetSystemLibrary 中的 Trace 有 LineTrace、BoxTrace、SphereTrace、CapsuleTrace 四种类型,每种又分为 xxxTraceSingle、xxxTraceMulti 两种(同上)。
举例说明:
bool UKismetSystemLibrary::LineTraceSingle(UObject* WorldContextObject, const FVector Start, const FVector End, ETraceTypeQuery TraceChannel, bool bTraceComplex, const TArray<AActor*>& ActorsToIgnore, EDrawDebugTrace::Type DrawDebugType, FHitResult& OutHit, bool bIgnoreSelf, FLinearColor TraceColor, FLinearColor TraceHitColor, float DrawTime)
{
ECollisionChannel CollisionChannel = UEngineTypes::ConvertToCollisionChannel(TraceChannel);
static const FName LineTraceSingleName(TEXT("LineTraceSingle"));
FCollisionQueryParams Params = ConfigureCollisionParams(LineTraceSingleName, bTraceComplex, ActorsToIgnore, bIgnoreSelf, WorldContextObject);
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
bool const bHit = World ? World->LineTraceSingleByChannel(OutHit, Start, End, CollisionChannel, Params) : false;
#if ENABLE_DRAW_DEBUG
DrawDebugLineTraceSingle(World, Start, End, DrawDebugType, bHit, OutHit, TraceColor, TraceHitColor, DrawTime);
#endif
return bHit;
}
可以看到,其实最后还是调的 World->LineTraceSingleByChannel
,UKismetSystemLibrary 里的 Trace 都是调用的 World 里的 Trace。从代码上看,调用对应关系如下(Single 和 Multi 对应关系显然):
UKismetSystemLibrary | World |
---|---|
LineTraceSingle / LineTraceMulti | LineTraceSingleByChannel / LineTraceMultiByChannel |
BoxTraceSingle | SweepSingleByChannel |
SphereTraceSingle | SweepSingleByChannel |
CapsuleTraceSingle | SweepSingleByChannel |
也就是 UKismetSystemLibrary 里默认是 ByChannel,也有对应其他方式的,比如 UKismetSystemLibrary::LineTraceSingleForObjects
对应 World->LineTraceSingleByObjectType
,其他同理。
即 Line 对应 Line,其他对应 Sweep。
UKismetSystemLibrary 的好处就是可以直接画出 Trace的起点、终点以及 Trace 的线:
e.g.
if (UKismetSystemLibrary::LineTraceSingleForObjects(
GetWorld(),
Location, /** Start */
FVector(Location.X, Location.Y, 0), /** End */
TArray<TEnumAsByte<EObjectTypeQuery>>{ UEngineTypes::ConvertToObjectType(ECC_WorldStatic) },
false, /** bTraceComplex */
{}, /** ActorsToIgnore */
EDrawDebugTrace::Persistent, /** DrawDebugType */
OUT HitResult, /** OutHit */
true)) /** bIgnoreSelf */
{
if (HitResult.bBlockingHit)
{
TargetLocation = HitResult.Location;
}
}