I'm confused about ClosestHitCollector

Hi,
I’m using Unity 2022.3.29f, Entity 1.0.16 and Physics 1.0.16

I want to use Physics to perform a target scan to attack,

I use OverlapSphere, iterate through hits and call ClosestHitCollector.AddHit on matching target,
but if the next hit’s distance is greater than the previous hit (my assumption), assertion in ClosestHitCollector.AddHit will throw error,
=> what is the point of using assertion here? can’t we just ignore the hit with distance greater than the previous hit?

so I thought I will write a custom collector and get rid of the assertion and, also, use OverlapSphereCustom for the target scan

var collector = new FilteredClosestHitCollector<DistanceHit>(attacker, filterHandler, weapon.Filter.ValueRO, 1000);
if (physics.OverlapSphereCustom(transformAttacker.Position, weapon.Range.ValueRO.Current, ref collector, CollisionFilter.Default)) {
   target = collector.ClosestHit.Entity;
}

but then again I got assertion error in another place: BoundingVolumeHierarchy.Distance,
I’m not sure why there is a distance check here…

my temporary solution: use OverlapSphere, iterate through hits and call (custom collector).AddHit on matching target

var target = Entity.Null;
var overlapHits = new NativeList<DistanceHit>(Allocator.Temp);
if (physics.OverlapSphere(transformAttacker.Position, weapon.Range.ValueRO.Current, ref overlapHits, CollisionFilter.Default)) {
   var collector = new FilteredClosestHitCollector<DistanceHit>(attacker, filterHandler, weapon.Filter.ValueRO, 1000);
   for (var i = 0; i < overlapHits.Length; i++) collector.AddHit(overlapHits[i]);
   overlapHits.Clear();
   target = collector.ClosestHit.Entity;
}

is there a more “elegant” solution? I mean can I skip the assertions mentioned above?

1 Like

I also got confused at first. According to Unity’s docs:

/// For casts this is fraction of the query at which the hit occurred. 
/// For distance queries, this is a distance from the query object.

The assertion in ClosestHitCollector happens because it expects the hit’s Fraction
(which represents the actual distance in distance queries) to be less than or equal to MaxFraction.
If a hit distance exceeds MaxFraction, the assertion will trigger.

Solution: make sure the collector’s MaxFraction is at least as large as the radius
you use in your OverlapSphere or distance query. This will prevent the assertion.

Example:

foreach (var (transform, botEntity) in
         SystemAPI.Query<RefRO<LocalTransform>>().WithAll<Bot>().WithEntityAccess())
{
    // Collector with MaxFraction = 5f
    var collector = new ClosestHitCollector<DistanceHit>(5f);
    var filter = new CollisionFilter
    {
        BelongsTo = ~0u,
        CollidesWith = 1u << canFightCollider,
        GroupIndex = 0
    };

    bool any = physicsWorld.OverlapSphereCustom(
        transform.ValueRO.Position,
        5f, // must be the same or greater than MaxFraction
        ref collector,
        filter
    );
}