通过 Physics Handle 抓取物体

在之前的文章 《FindComponentByClass()》 中,我们已经添加了 Physics Handle 组件,并且能通过代码获取到它。

在这篇文章中,我们学习如何使用 Physics Handle 抓取物体。我们先了解 Physics Handle 的两个成员函数:GrabComponentAtLocationWithRotation()SetTargetLocationAndRotation()

GrabComponentAtLocationWithRotation() 函数抓取一个物理组件,并将其定位到指定的位置,同时可以附带旋转。其函数定义如下:

  • void UPhysicsHandleComponent::GrabComponentAtLocationWithRotation(
  •     class UPrimitiveComponent* Component,
  •     FName InBoneName,
  •     FVector GrabLocation,
  •     FRotator Rotation);

Component 参数指定要抓取的物理组件;InBoneName 参数指定附带的骨骼组件的名称,没有的话指定为 NAME_None;GrabLocation 参数指定要被抓取的位置;Rotation 参数指定抓取时物体的旋转角度。

SetTargetLocationAndRotation() 函数用于在物体已经被抓取之后,实时调整物体的位置和旋转。其函数定义如下:

  • void UPhysicsHandleComponent::SetTargetLocationAndRotation(
  •     FVector NewLocation,
  •     FRotator NewRotation);

以上的 Rotation 参数含义都不太清楚。

了解好函数的使用之后,我们开始实现代码。如代码清单 1 所示,我们先调用 GrabComponentAtLocationWithRotation():抓取的位置是扫掠检测发生碰撞的位置。

代码清单 1 GrabComponentAtLocationWithRotation
  1. void UGrabber::Grab()
  2. {
  3.     if (PhysicsHandle != NULL)
  4.     {
  5.         FVector Start = GetComponentLocation();
  6.         FVector End = Start + GetForwardVector() * MaxGrabDistance;
  7.  
  8.         FCollisionShape Sphere = FCollisionShape::MakeSphere(GrabRadius);
  9.         FHitResult HitResult;
  10.         bool HasHit = GetWorld()->SweepSingleByChannel(HitResult,
  11.             Start, End,
  12.             FQuat::Identity,
  13.             ECC_GameTraceChannel2,
  14.             Sphere);
  15.  
  16.         if (HasHit)
  17.         {
  18.             PhysicsHandle->GrabComponentAtLocationWithRotation(
  19.                 HitResult.GetComponent(),
  20.                 NAME_None,
  21.                 HitResult.ImpactPoint,
  22.                 GetComponentRotation()
  23.             );
  24.         }
  25.     }
  26. }

接着如代码清单 2 所示,我们在场景组件的 TickComponent 函数中,不断调用 SetTargetLocationAndRotation() 更新抓取物体的位置和旋转。更新的位置在当前视角位置向前指定的距离(通过 HoldDistance 变量指定)。

代码清单 2 SetTargetLocationAndRotation
  1. // Called every frame
  2. void UGrabber::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
  3. {
  4.     Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
  5.  
  6.     if (PhysicsHandle != NULL)
  7.     {
  8.         FVector TargetLocation = GetComponentLocation() + GetForwardVector() * HoldDistance;
  9.         PhysicsHandle->SetTargetLocationAndRotation(
  10.             TargetLocation, GetComponentRotation()
  11.         );
  12.     }
  13. }

以上的 Rotation 参数含义都不太清楚。目前都设置为场景组件当前的旋转,看着效果是可以的。

在运行程序之前,我们需要把待抓取物体的 移动性 属性设置为 可移动;并把 模拟物理 属性勾选启动。否则物体无法被抓取。

不要忘记设置。无法抓取也没有什么日志提示。

最终的效果如下方视频所示,可以看到抓取的移动过程是满足物理效果的,会被墙等物体挡住。