Physics Handle 释放物体

在上一篇文章中,我们已经通过 Physics Handle 实现了物体的抓取。在这篇文章中,我们会实现物体的释放。

在实现之前,我们先介绍一种特殊情况。在 Unreal Engine 中,如果一个刚体静止久了,为了节省性能,它会被暂停计算,进入“沉睡”状态。为了能继续进行运动模拟,需要调用该物体的 UPrimitiveComponent::WakeAllRigidBodies() 函数,使其重新参与物理计算,即“唤醒”它。

如代码清单 1 所示,我们完善了之前写的抓取逻辑。在抓取物体之前,我们先唤醒它,以免抓取无效。

自己本地环境没有复现出抓取不了的情况。不过增加唤醒逻辑肯定是好的。

代码清单 1 完善抓取
  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.             UPrimitiveComponent* HitComponent = HitResult.GetComponent();
  19.             HitComponent->WakeAllRigidBodies();
  20.             PhysicsHandle->GrabComponentAtLocationWithRotation(
  21.                 HitComponent,
  22.                 NAME_None,
  23.                 HitResult.ImpactPoint,
  24.                 GetComponentRotation()
  25.             );
  26.         }
  27.     }
  28. }

接着,我们实现释放逻辑。在之前的文章 《输入操作映射》 中,我们已经定义了动作逻辑:鼠标左键按下抓取物体,左键弹起释放物体。

如代码清单 2 所示,我们首先调用 UPhysicsHandleComponent::GetGrabbedComponent() 函数,判断是否有抓取的物体。如果有抓取物体的话,我们还是先唤醒它,然后调用 UPhysicsHandleComponent::ReleaseComponent() 释放它。

代码清单 2 释放逻辑
  1. void UGrabber::Release()
  2. {
  3.     UE_LOG(LogTemp, Display, TEXT("Released grabber"));
  4.     if (PhysicsHandle != NULL)
  5.     {
  6.         UPrimitiveComponent* GrabbedComponent = PhysicsHandle->GetGrabbedComponent();
  7.         if (GrabbedComponent != NULL)
  8.         {
  9.             GrabbedComponent->WakeAllRigidBodies();
  10.             PhysicsHandle->ReleaseComponent();
  11.         }
  12.     }
  13. }

我们已经知道有函数可以判断是否有抓取物体。如代码清单 3 所示,在没有抓取物体的时候,我们就不需要更新抓取物体的位置和旋转信息。

代码清单 3 完善 tick 逻辑
  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.         if (PhysicsHandle->GetGrabbedComponent() != NULL)
  9.         {
  10.             FVector TargetLocation = GetComponentLocation() + GetForwardVector() * HoldDistance;
  11.             PhysicsHandle->SetTargetLocationAndRotation(
  12.                 TargetLocation, GetComponentRotation()
  13.             );
  14.         }
  15.     }
  16. }

此时我们可以运行程序进行查看了。可以发现还有需要完善的点:如果我们抓着物体靠着墙,会发现物体的行为非常奇怪,这是因为抓取的物体和我们控制的角色也发生了碰撞。

为了修复这个问题,如图 1 所示,我们来到雕塑组件的碰撞预设选项。在文章 《射线追踪与扫掠检测》 中,我们已经创建了一个自定义的碰撞预设。修改其下和 Pawn 类型物体的响应方式为 重叠

图1 碰撞预设

之前为了防止雕塑底部看着镂空,在其底部加了一块粘土。为了方便,我们将这块粘土的碰撞预设直接修改为内置提供的 NoCollision

最终运行的效果如下方视频所示,我们按下左键抓取物体;然后按住左键不弹起,可以让物体跟随移动;最后弹起左键可以释放物体。