Without going into too much detail, I have some code that runs both Client and Server side to check whether a location is valid for spawning an object.
This is the general flow of it:
- Client takes aim and figures out where to ‘spawn’ the two objects.
- OIf the client deems those locations as ‘safe’, it sends two FVectors to the Server (objects are spheres, orientation doesn’t matter). For debugging’s sake, I’m not using any net-optimized vectors or anything.
- Server runs the same ‘Safety Check’ code on those two vectors and if safe, spawns the two objects. If not, it tells the client that it’s safety check failed.
Here’s the problem - the client succeeds these checks for safe collision when it’s supposed to / as expected, but the Server inexplicably fails around ~50% of the time. I’ve tried this in a simple test level and I’m having zero luck. Both Client and Server run identical code. I am NOT testing against any dynamic objects, but against static level geometry that is identical on Server and Client side.
Simply put, overlap detection between two static objects with identical transforms seems to inexplicably fail on a Server for no reason as far as I can tell.
Here is the code. It’s very similar to what happens in LevelActor.cpp for spawning an object. The only place this can fail is the If/Else statement. Why would this fail on the Server more than the remote client, PhysX errors or something deeper?
bool UECGame_QuantumTunnel::GetSafeSpawnLocation(const FVector& InAimLocation, FVector& OutSafeLocation)
{
// We want to do a spherical trace outwards, and work out where to place the sphere given intersecting geometry
const FVector DesiredSpawnLocation = InAimLocation;
OutSafeLocation = DesiredSpawnLocation;
TArray<FOverlapResult> Overlaps;
const ECollisionChannel BlockingChannel = ECC_Portal;
const FCollisionShape CollisionShape = FCollisionShape::MakeSphere(CachedPortalRadius + QuantumTunnelConfig.PortalPlaceSafeZone);
const FCollisionQueryParams QParams = FCollisionQueryParams(NAME_SafeQuantumPlacement, false);
// Test all channels, we have to filter out ourselves (or make a new collision channel)
const bool bFoundBlock = GetWorld()->OverlapMultiByObjectType(Overlaps, DesiredSpawnLocation, FQuat::Identity, FCollisionObjectQueryParams(FCollisionObjectQueryParams::InitType::AllObjects), CollisionShape, QParams);
if (bFoundBlock)
{
FVector ProposedAdjustment = FVector::ZeroVector;
FMTDResult MTDResult;
uint32 NumBlockingHits = 0;
for (int32 HitIdx = 0; HitIdx < Overlaps.Num(); HitIdx++)
{
UPrimitiveComponent* OverlapComponent = Overlaps[HitIdx].GetComponent();
if (OverlapComponent && OverlapComponent->GetCollisionResponseToChannel(BlockingChannel) == ECR_Block)
{
NumBlockingHits++;
const bool bSuccess = OverlapComponent->ComputePenetration(MTDResult, CollisionShape, DesiredSpawnLocation, FQuat::Identity);
if (bSuccess)
{
ProposedAdjustment += MTDResult.Direction * MTDResult.Distance;
}
else
{
UE_LOG(LogECPortal, Warning, TEXT("Quantum Tunnel invalid adjusted collision for %s. Dist: %f"), *GetNameSafe(OverlapComponent), MTDResult.Distance);
return false;
}
}
}
if (NumBlockingHits == 0 || ProposedAdjustment.IsZero())
{
return true;
}
else
{
OutSafeLocation += ProposedAdjustment;
return true;
}
}
else
{
return true;
}