The blue dot // pivot point indicates the point from where the range is being calculated

What solutions have you tried so far?
For now, I subtract the âraw distanceâ of the two objects with the radius:

function module.distanceBetween(self:BasePart, target: BasePart): number
local s1 = self.Size
local s2 = target.Size
local radius1 = math.sqrt((s1.X/2)^2 + (s1.Z/2)^2)
local radius2 = math.sqrt((s2.X/2)^2 + (s2.Z/2)^2)
local diff = self.Position - target.Position
-- radius -> center to any edge
return diff.Magnitude - radius1 - radius2
end

This would work well except for objects that are much longer on one axis; For example a wall:

The intersection between green (object radius) and red (weapon range) shows that the turret can shoot the wall. (Since we now subtract the object radius from the distance to get the final distance)

This gives the impression that the turret can shoot much farther than the range implied by the red circle

Goals

In simple terms, how do I make the turret be in range of the Repair Bay, white also be out of range of the wall?

In your scenario, you can use the red/green cylinders as your target and origin parts. You might have to switch the X to Y or Z, this is because cylinders lay down flat on default

Thatâs because the script isnât accounting for if the object itself is inside the radius, itâs only checking to see if the other objectâs radiusâ are overlapping with the turretâs radius.

I suggest you use Raycasting to see if an object is within the ray of the turret.

function module.castRay(turret)
local turretSize = turret.Size
local turretPosition = turret.Position
local turretDirection = math.sqrt((s1.X/2)^2 + (s1.Z/2)^2)
local Ray = Ray.new(turretPosition, turretDirection)
local hit, position = workspace:FindPartOnRay(Ray, turret)
end

This method will probably require more checks for things like ensuring that the turret isnât shooting the floor or itself, but itâs the method that games usually use for things like this. However, if @JonYawnsâ way works efficiently enough for you then use that instead!

Oh!
I have thought of using raycasting before, but I wouldnât want 50+ units raycasting to everyone else every 0.5 seconds just to find the nearest target, but reading your reply made me realise, I just have to raycast to the nearest one it can find â

function module.GetTarget(origin: Vector3)
local nearest = module.getClosestTarget(origin) -- previous distance comparison function
if nearest == nil then return nil end
local param = RaycastParams.new()
param.FilterDescendantsInstances = {map, nearest}
param.FilterType = Enum.RaycastFilterType.Whitelist
local ray = workspace:Raycast(origin, diff.Unit * range, param)
return (ray and ray.Instance:IsDescendantOf(nearest) and nearest) or (ray == nil and nil)
end)

But this runs into another problem:
Since the distance comparison function returns the closest target, it wonât change the target if the ray doesnât hitâŚ leading to it forever trying to hit a wall even when a smaller object is closer than the wall

Ok I solved the part where the âtrying to get to same target foreverâ problem by:

Get all the nearest targets

Sort the nearest targets from closest to farthest

Repeat raycast check until a valid target is found

function module.GetTarget(self:BasePart, origin: Vector3)
module.checkParam = module.checkParam or RaycastParams.new()
local param = module.checkParam
local nearby = module.GetNearbyTargets(origin, range, function(part, diff)
if part == self then return false end
return true
end)
table.sort(nearby, function(a: BasePart,b: BasePart) -- Sort the table from closest to farthest
local diffA = (a.Position - origin)
local diffB = (b.Position - origin)
return Vector.SquaredMagnitude(diffA) < Vector.SquaredMagnitude(diffB)
end)
local target = nil
param.FilterDescendantsInstances = {self, self.Parent}
param.FilterType = Enum.RaycastFilterType.Blacklist
while target == nil and #nearby > 0 do
local nextTarget = table.remove(nearby, 1)
local ray = workspace:Raycast(origin, diff.Unit*range, param)
target = (ray and getUnitFromPart(ray.Instance)) or nil
end
return target
end