How do I get 'Outside Distance' between two parts?

  1. What do you want to achieve?
    I have a code that compares the distance of two objects by subtracting their positions and getting the magnitude.

  2. What is the issue?
    This works well except that you have to get really close to an object if their size is relatively big :confused: :

  • The red sphere indicates the range of the turret
  • The blue dot // pivot point indicates the point from where the range is being calculated
  1. 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 turret can shoot the Repair Bay now, Yay! :smiley:

  • Somehow it can shoot a wall outside its range???

  • 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

  1. Goals
2 Likes

Assuming that both the origin and target part has the same X and Z size values, you can use this.

return (origin.Position - target.Position).Magnitude - origin.Size.X/2 - target.Size.X/2

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

2 Likes

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

1 Like

That’s when the problem I’m trying to solve happens, when the X and Z size values are not the same.

TheSuzerain below gave me insight to solve it, just letting you know :slight_smile:

Edit:

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

  1. Get all the nearest targets
  2. Sort the nearest targets from closest to farthest
  3. Repeat raycast check until a valid target is found
    :smiley:
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
1 Like