几何体扫掠检测

在这篇文章中,我们开始实现扫掠检测。扫掠检测的概念在之前的文章中已经了解过:可以想象成一个几何体(比如球体)从一个起点沿着某个路径“拖动”或“扫过”,并检测它在这个过程中是否与其他物体发生碰撞。

此处,我们使用 Unreal Engine 中的 SweepSingleByChannel() 函数,它用于单次几何体碰撞检测。

Single 单次的意思是,只关心第一次碰撞的物体。

与之对应的有 SweepMultiByChannel,它会检查路径上所有可能的碰撞。

SweepSingleByChannel 的函数定义如下:

  • bool UWorld::SweepSingleByChannel(struct FHitResult& OutHit,
  •     const FVector& Start,
  •     const FVector& End,
  •     const FQuat& Rot,
  •     ECollisionChannel TraceChannel,
  •     const FCollisionShape& CollisionShape,
  •     const FCollisionQueryParams& Params /* = FCollisionQueryParams::DefaultQueryParam */,
  •     const FCollisionResponseParams& ResponseParam /* = FCollisionResponseParams::DefaultResponseParam */) const;

OutHit 是一个输出参数,用于存储碰撞检测的结果。

Start 指定要扫掠的起始位置;End 指定要扫掠的目标位置。

Rot 指定扫掠物体的旋转信息。

TraceChannel 指定碰撞通道。

CollisionShape 指定进行扫掠检测的几何体形状。

Params 指定查询参数,用于定制碰撞检测的行为。有默认值。

ResponseParam 指定碰撞响应的行为。有默认值。

返回值是布尔值,表示是否发生了碰撞。

在了解了 SweepSingleByChannel 各个参数的含义之后,我们实现以下功能:使用球体沿着相机视线的方向扫掠,打印出碰撞的物体信息。

如代码清单 1 所示,我们使用 FCollisionShape::MakeSphere 创建一个球体。球体不需要旋转,旋转信息使用 FQuat::Identity,当然球体即使旋转了也没有区别。扫掠的起始点和终点,在上一篇文章中已经可视化看过。

代码清单 1 SweepSingleByChannel
  1. // Called every frame
  2. void UGrabber::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
  3. {
  4.     Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
  5.  
  6.     FVector Start = GetComponentLocation();
  7.     FVector End = Start + GetForwardVector() * MaxGrabDistance;
  8.  
  9.     DrawDebugLine(GetWorld(), Start, End, FColor::Red);
  10.  
  11.     FCollisionShape Sphere = FCollisionShape::MakeSphere(GrabRadius);
  12.     FHitResult HitResult;
  13.     bool HasHit = GetWorld()->SweepSingleByChannel(HitResult,
  14.         Start, End,
  15.         FQuat::Identity,
  16.         ECC_GameTraceChannel2,
  17.         Sphere);
  18.  
  19.     if (HasHit)
  20.     {
  21.         AActor* HitActor = HitResult.GetActor();
  22.         UE_LOG(LogTemp, Display, TEXT("Hit actor: %s"), *HitActor->GetActorNameOrLabel());
  23.     }
  24.     else
  25.     {
  26.         UE_LOG(LogTemp, Display, TEXT("No actor hit"));
  27.     }
  28. }

SweepSingleByChannel 函数中的通道指定需要注意。函数中通道是一个枚举值,我们想要将通道指定为之前自己创建的 Grabber 通道,该如何指定呢?

我们可以打开项目根目录下的 Config\DefaultEngine.ini 文件,搜索 Grabber。可以看到如下项,填写其中的 Channel 值即可。

  • +DefaultChannelResponses=(Channel=ECC_GameTraceChannel2,DefaultResponse=ECR_Ignore,bTraceType=True,bStaticObject=False,Name="Grabber")

运行程序。当我们视角对着雕塑的时候,如图 1 所示,可以看到日志里打印了雕塑物体的名称。

图1 扫掠检测