最近碰到了和这个老哥类似的问题,任何一个继承自AVolume的子类在判断Actor->GetActorLocation是否被Volume覆盖的时候总是返回false,即使是明显的case。
前情提要大概是需要在Build HLOD的时候对部分区域Actor进行特殊处理,所以从AVolume继承了一个子类,然后通过摆盒子来圈定空间范围。
Debugging
深入debug了下,发现从某个版本开始AVolume::EncompassesPoint换掉了基于AABB检查的方法,而是依靠物理引擎来判断是否有交集。

注意这里判断点是否位于Volume内都是用的GetSquaredDistanceToCollision。
里面实际上是用的物理引擎判断的,如果Volume没有碰撞,这里会返回false,坑了我一天时间。
bool UPrimitiveComponent::GetSquaredDistanceToCollision(const FVector& Point, float& OutSquaredDistance, FVector& OutClosestPointOnCollision) const
{
OutClosestPointOnCollision = Point;
FBodyInstance* BodyInst = GetBodyInstance();
if (BodyInst != nullptr)
{
return BodyInst->GetSquaredDistanceToBody(Point, OutSquaredDistance, OutClosestPointOnCollision);
}
return false;
}
一个Actor要在物理引擎里使用,必须要用Physics State(类似于Render State),即这个Actor在物理世界的代理体。
为什么ACullDistanceVolume什么都不用做就能工作?
一个Component是否创建Physics State一个统一的入口。
判断条件有两个:
- 设置了Collision Profile, 或者
- Component 带有bAlwaysCreatePhysicsState标记
bool UPrimitiveComponent::ShouldCreatePhysicsState() const
{
if (IsBeingDestroyed())
{
return false;
}
bool bShouldCreatePhysicsState = IsRegistered() && (bAlwaysCreatePhysicsState || BodyInstance.GetCollisionEnabled() != ECollisionEnabled::NoCollision);
...
return bShouldCreatePhysicsState;
}
回过头来看,为什么虚幻默认的CullVolume直接拖到场景就能生效,并不需要任何额外的操作。
ACullDistanceVolume::ACullDistanceVolume(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
GetBrushComponent()->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
GetBrushComponent()->bAlwaysCreatePhysicsState = true;
....
}
原来是设置了bAlwaysCreatePhysicsState,那就不奇怪了。
结论
虚幻的某次把AVolume的判定改动为依靠物理引擎,但是没有给AVolume的创建PhysicsState。 所以任何继承AVolume的子类(包括引擎里一些4.X时期的AVolume的子类没有适配),要用的时候都需要留意,PhysicsState是否已经创建。