(lengthy source code snippets below, beware…)
I had a similar problem, in my case it was all the different components I had on my pawns that were inevitably expanding their bounding boxes to beyond what I’d expect. My solution was to subclass AHUD::GetActorsInSelectionRectangle and simply remove checking for anything but the root component. Since Pawns and Characters in general only use their root component for collision anyway, I think my approach makes sense. This got it down to 99% precision.
It’s very similar to GetActorsInSelectionRectangle(), in fact all you need to do is copy-paste the whole thing and replace this one line:
const FBox EachActorBounds = EachActor->GetComponentsBoundingBox(bIncludeNonCollidingComponents); /* All Components? */
with this:
const FBox EachActorBounds = EachActor->GetRootComponent()->Bounds.GetBox();
Just for clarity’s sake I’m including the complete function below, .cpp and .h. Hope this helps!
Here’s the relevant part from the .h file of my custom HUD class in c++:
public:
// Similar to GetActorsInSelectionRectangle() but only takes into account the root component of each actor.
UFUNCTION(BlueprintPure)
void GetActorsInSelectionRect(TSubclassOf<AActor> ClassFilter, const FVector2D& FirstPoint, const FVector2D& SecondPoint, TArray<AActor*>& OutActors, bool bActorMustBeFullyEnclosed = false);
And now for the .cpp:
void AYourHUD::GetActorsInSelectionRect(TSubclassOf<class AActor> ClassFilter, const FVector2D& FirstPoint, const FVector2D& SecondPoint, TArray<AActor*>& OutActors, bool bActorMustBeFullyEnclosed)
{
// Because this is a HUD function it is likely to get called each tick,
// so make sure any previous contents of the out actor array have been cleared!
OutActors.Reset();
//Create Selection Rectangle from Points
FBox2D SelectionRectangle(ForceInit);
//This method ensures that an appropriate rectangle is generated,
// no matter what the coordinates of first and second point actually are.
SelectionRectangle += FirstPoint;
SelectionRectangle += SecondPoint;
//The Actor Bounds Point Mapping
const FVector BoundsPointMapping[8] =
{
FVector(1.f, 1.f, 1.f),
FVector(1.f, 1.f, -1.f),
FVector(1.f, -1.f, 1.f),
FVector(1.f, -1.f, -1.f),
FVector(-1.f, 1.f, 1.f),
FVector(-1.f, 1.f, -1.f),
FVector(-1.f, -1.f, 1.f),
FVector(-1.f, -1.f, -1.f) };
//~~~
//For Each Actor of the Class Filter Type
for (TActorIterator<AActor> Itr(GetWorld(), ClassFilter); Itr; ++Itr)
{
AActor* EachActor = *Itr;
//Get Actor Bounds //casting to base class, checked by template in the .h
const FBox EachActorBounds = EachActor->GetRootComponent()->Bounds.GetBox();
//Center
const FVector BoxCenter = EachActorBounds.GetCenter();
//Extents
const FVector BoxExtents = EachActorBounds.GetExtent();
// Build 2D bounding box of actor in screen space
FBox2D ActorBox2D(ForceInit);
for (uint8 BoundsPointItr = 0; BoundsPointItr < 8; BoundsPointItr++)
{
// Project vert into screen space.
const FVector ProjectedWorldLocation = Project(BoxCenter + (BoundsPointMapping[BoundsPointItr] * BoxExtents));
// Add to 2D bounding box
ActorBox2D += FVector2D(ProjectedWorldLocation.X, ProjectedWorldLocation.Y);
}
//Selection Box must fully enclose the Projected Actor Bounds
if (bActorMustBeFullyEnclosed)
{
if (SelectionRectangle.IsInside(ActorBox2D))
{
OutActors.Add(EachActor);
}
}
//Partial Intersection with Projected Actor Bounds
else
{
if (SelectionRectangle.Intersect(ActorBox2D))
{
OutActors.Add(EachActor);
}
}
}
}
The one time you may run into problems (unwanted extra units being selected) is when they overlap each other from your viewport’s point of view. That would probably require some traces to correct for…