1
\$\begingroup\$

I want to be able to do range checks against the entirety of an enemy object, and not just it's transform.position. The enemy object can have it's side or nose within range, but range detection will not determine if it's close enough to be targeted unless it's transform.position is within range.

enter image description here

Right now I run a quick check against all nearby units to determine their distance from a turret on the friendly unit.

I was thinking raycasts, but I imagine raycasts every frame for up to 10 detected enemies per unit (Each unit will at max hold onto references of 10 nearby enemies. I could have 100 friendly units within range of 100 enemies) would chew up a fair bit more CPU than just checking their cached transforms.

Suggestions, ideas, or comments?

Edit: More Detail

Currently a Unit has a trigger spherical collider that defines it's view range. Any enemy unit within this view range is added to a collection based on priority, with a maximum of 10 objects referenced per collection (each Unit can only be aware of 10 enemy objects). From there, each turret on ship checks the distance from itself to each of the enemies within that collection. When the distance is <= it's attack range, it will fire upon whichever enemy object gets within it's attack range first.

I used to use spherical colliders for each turret, which of course would detect the enemy as soon as any part of the enemy entered the trigger collider. The issue was the massive performance loss due to OnTriggerStay() begin called by hundreds of units, tens of thousands of times per frame when I have no need for the method. So I discarded the extra trigger colliders for a more performance friendly approach.

The issue is that the range check is that is checks the transform.position of the enemy. This does not take into account the collider that makes up the enemies shape, and will only trigger when the center of the object enters within range.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Would a bounding sphere be a sufficient approximation? You can check this very quickly/cheaply, and correctly account for larger/smaller entities as long as their sizes are similar across all axes. For long & skinny entities, an oriented bounding box check is only slightly more expensive, and lets you account for a wider variety of entity shapes. \$\endgroup\$
    – DMGregory
    Apr 30, 2015 at 23:12
  • \$\begingroup\$ When you say a bounding sphere/box, do you mean a built-in sphere collider or box collider within Unity? If so I'm avoiding the use of more than 1 trigger collider per unit due to performance issues with OnTriggerStay(). I added more detail to the bottom of my post. \$\endgroup\$ Apr 30, 2015 at 23:25
  • 1
    \$\begingroup\$ If the length of the vector between their centers is less than (radius + enemyWidth), the enemy is in range. \$\endgroup\$
    – Jon
    Apr 30, 2015 at 23:30
  • 1
    \$\begingroup\$ These types of checks don't rely on a collider, if you don't mind writing a few lines of math yourself. Jon gives an example above of how simple this is for a bounding sphere, and a bounding box isn't much more complex. (Also, nice to meet a fellow Douglas G. ;) ) \$\endgroup\$
    – DMGregory
    Apr 30, 2015 at 23:45
  • \$\begingroup\$ Ah, that makes sense Jon. I'll just need to figure out the distance from the center of that object to it's outer edge along the vector from the turret to the center of that object. I should be able to figure that out. Oh, nice to mean another Douglas G as well, even the same nationality \o/. Thanks for the help guys, if one of you wants to post an answer I can mark it for you. \$\endgroup\$ May 1, 2015 at 0:36

2 Answers 2

2
\$\begingroup\$

Two dot products, using the vector between their centers, tells you which corner is closest.
A positive "Forward" dot indicates a front corner; negative is rear.
A positive "Right" dot indicates a right corner; negative is left.
If the distance between the selected corner and the friendly is less than Range, it is in-range.

Nearest-corner test:

enter image description here

This will detect collisions that aren't caught by the nearest-corner-test:
(Diagramming purple was a mistake since it would have already passed the nearest-corner-test. The calculation will work for all cases, including perpendicular, as shown in white.
For the perpendicular, (Width / 1.0) * 1.0 == Width.)

Re-use the dots from the nearest-corner-test to find the nearest "cardinal" enemy direction (the larger of the two). Calculate the hypotenuse length and, if (DistanceBetweenCenters - HYP) <= Radius, the enemy is within range.

enter image description here

Edited to include theory and attempted to make better use of color:

enter image description here

\$\endgroup\$
12
  • \$\begingroup\$ Thanks, I just got some time together and implemented it. Here is a paste of my first method that works: pastebin.com/FpsNEqDT. It's a bit oversized for a method and will be broken down. However I'm a bit dubious about the performance difference between it and a raycast now, there is quite a bit going on in there. It would probably be great if I handed off the math to another thread for 100's of units, but I'm not that far. I will set up a scene for profiling and report back on my findings vs raycasts. \$\endgroup\$ May 2, 2015 at 8:13
  • \$\begingroup\$ I'm also pretty sure how to make the checks more granular. For instance checking a 6-pointed shape instead of a 4-pointed shape for more accurate results. Just requires more checks and dot products. \$\endgroup\$ May 2, 2015 at 8:21
  • \$\begingroup\$ Just tested it with 100,000 calculations per frame(component caching was pretty important at these numbers). It chopped off 14.8ms of frame time, while 1,000 raycasts chopped off 135ms of frametime. I would have to perform 1,000,000 of these methods per frame to equal the performance cost of 1,000 raycasts. In other words, this is 3 orders of magnitude more efficient, bravo. \$\endgroup\$ May 2, 2015 at 8:41
  • \$\begingroup\$ Hey Jon, out of curiosity, would there be a way to get the distance for an object like the grey one in your image where there is no corner within range, yet part of the body is. Without a custom mesh with extra points (Like a hexagon stretched to fit the form of the ship). \$\endgroup\$ May 3, 2015 at 6:42
  • 1
    \$\begingroup\$ @douglasg14b, I've added a blow-up and additional details. \$\endgroup\$
    – Jon
    May 3, 2015 at 8:08
2
\$\begingroup\$

Unity provides this functionality out of the box with Collider.ClosestPointOnBounds and Rigidbody.ClosestPointOnBounds (for compound colliders).

Testing whether an object is in range is as simple as:

bool IsInRange(GameObject target, Vector3 origin, float rangeSquared) {
    Vector3 closest = target.GetComponent<Rigidbody>().ClosestPointOnBounds(origin);

    return ((closest - origin).sqrMagnitude <= rangeSquared);
}

(Note that I'm using sqrMagnitude and compare the result to "range squared" because it's faster, especially if the range doesn't change often)

Or, as DMGregory pointed out, you could use Physics.OverlapSphere / Physics.OverlapSphereNonAlloc to check with a sinlge call:

void ProcessAllInRange(Vector3 center, float radius)
{
    Collider[] hits = Physics.OverlapSphere(center, radius);
    int i = 0;
    while (i < hits.Length)
    {
        //do work
        i++;
    }
}

It is worth mentioning that Physics.OverlapSphere (as far as I can tell) checks for actual collisions, while Rigidbody.ClosestPointOnBounds uses the compound collider's (axis aligned) bounding box.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ This answer would be even better if it included a rough outline of how you'd use these methods to solve the original problem (ie. First get a point from ClosestPointOnBounds and then check the range to that). Although, if all your targets have colliders, you could do this in one call with Physics.OverlapSphere. \$\endgroup\$
    – DMGregory
    Nov 29, 2017 at 13:20
  • \$\begingroup\$ Good point, I'll add examples later today. \$\endgroup\$ Nov 29, 2017 at 13:34

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .