Most performant way to handle actor proximity on scale

Hi all,

I’m currently looking for advice on handling actor-to-actor proximity in the most performant manner, based on a radius. I’ve seen the common debate of using raycasts vs geometry based collisions vs native AIPerception. I will potentially have on average around a hundred actors at any one time and need a way for each actor to determine if another(others) have entered an arbitrary vision radius so that I can issue an attack, or manage priority etc. Any advice would be appreciated.

assuming you need line of sight i dont see a way around linetraces. i use a collision sphere for range and line traces for line of sight but you can be clever and only update when an actor moves or something

I’ve read that maintaining the actual collision geometry in relation to the actor is rather expensive, and doesn’t scale well? I can’t imagine there being a more performant way of verifying the ‘entering’ and ‘leaving’ of an actor within a radius than the one you just suggested, without having to do linetraces (async even) at a sub second interval.

Hi, if you just want to check the distance and not line of sight, then there is no need for using collision. Just register all the actors with a manager and then check the distances there. So e.g. create a component that registers with the manager and inside the manager for each component check the distance of its owner to all the other components owners. With a hundred actors that would be 10000 distance checks, which should be pretty fast in c++, especially if you stretch the work over several frames.

Also as a fast to implement solution you could use the sight sense and overwrite the CanBeSeenFrom function to just return true, that will skip the linetrace and will then only check based on distance and dot product.

Are you referring to using a form of spatial partitioning? I could potentially use that to eliminate the exponential actor-to-actor distance checks and just track and alert relevant actors with delegates. I’ve read how powerful Octree’s can be for centralising actor locations and interactions, and I’m very open to exploring that possibility if it’ll let me squeeze out every bit of performance considering the scale I’m imagining.

No, maybe I misunderstood something, but I understood your problem that you want to check distances between actors, and based on that distance do something (e.g. trigger when a new actor gets into distance or leaves distance). But not that you want to also trigger based on line of sight, so only by distance.
My point was just, that if you do not need line of sight, then you do not need raycasts or geometry based collisions which are both costly, but can just check the distances (100 actors against 100 other actors would be 10000 checks, which should be no problem in c++ performance wise).

Honest question: getting overlapped actors in a sphere for 100 actors is less performant than 10,000~ math ops?

I never benchmarked it (I only once tested linetraces, there 100 on tick were fine, 1000 were noticeable), I just always assumed that sphere traces are costly :sweat_smile:

Now I checked it, running this code on tick:

Super::Tick(DeltaTime);

if (bDoDistanceChecks)
{
    FRandomStream rand;
    rand.GenerateNewSeed();
    j = 0;

    for (int i = 0; i < numTraces * numTraces; i++)
    {
        FVector A = rand.GetUnitVector();
        FVector B = rand.GetUnitVector();

        if (FVector::Dist(A, B) > 10)
        {
            j += 1;
        }
    }
}
else
{
    FHitResult OutHit;

    for (int i = 0; i < numTraces; i++)
    {

        UKismetSystemLibrary::SphereTraceSingleForObjects
        (
            this,
            FVector(0.0f, 0.0f, 0.0f),
            FVector(0.0f, 0.0f, 0.0f),
            Radius,
            ObjectTypes,
            false,
            TArray< AActor* >(),
            EDrawDebugTrace::None,
            OutHit,
            false
        );
    }
}

So I have both the distance checks or sphere traces in one actor, toggling between them with the bDoDistanceChecks boolean. Those were the results:

all tests play in editor
default third person map from starter content ue 5.3.1
running stat game window

1.5ms for 100 sphere traces radius 10000
14.2ms for 1000 sphere traces radius 10000
0.3ms for 100 sphere traces radius 1000
3.2ms for 1000 sphere traces radius 1000

0.4ms for 100*100 distance checks
42ms for 1000*1000 distance checks


-----------------------------


map I am currently working on, so a larger map with more stuff in it

1.4ms for 100 sphere traces radius 1000
846ms for 100 sphere traces radius 10000

43ms for 1000*1000 distance checks (just as a sanity check, the distance checks time should be constant, regardless of the map)

The times above come from subtracting the default game thread time, without that actor running its logic on tick.

1 Like

Could you run same test with Sphere Overlap Actors rather than a sphere trace?

Sure, using this code here in the for loop:

        TArray< class AActor* > OutActors;

        UKismetSystemLibrary::SphereOverlapActors
        (
            this,
            FVector(0.0f, 0.0f, 0.0f),
            Radius,
            ObjectTypes,
            ActorClassFilter,
            TArray< AActor* >(),
            OutActors
        );

Gives those times here:

default third person map

SphereOverlapActors for static mesh actor (30 instances)
1.3ms for 100 traces radius 10000




larger map


SphereOverlapActors for an actor class that has 45 instances with one static mesh and one collision sphere
0.5ms for 100 traces radius 1000
92ms for 100 traces radius 10000

SphereOverlapActors for player class (one instance only)
90ms for 100 traces radius 10000


0.5ms-0.3ms-not really measurable cost when using pawn collision channel (all other tests so far I used WorldStatic), with 46 instances having that channel
for 100 traces radius 10000



That is faster. And I guess what I should have started to begin with, is to trace for a collision channel that only (in the test case around 46) actors have and not always use WorldStatic :sweat_smile:

So when using a collision channel that only those 100 actors will have, it won’t have much performance cost using sphere overlap.

2 Likes

Whilst this is very positive, isn’t the cost of geometry collision not in the collision check itself but the actual tracking and relative transformation of the geometry in relation to the actor? Still looks like my best bet at the moment though given the little benchmark done.