Page 1 of 1

Finding which object in the table is closest

Posted: Wed Feb 28, 2024 10:27 am
by Griff
So I haven’t started to implement attacks yet since I’m not sure how I would figure out which enemy is actually closest to my tower. Basically I want to find the enemy closest to the tower and fire an arrow at them.

Re: Finding which object in the table is closest

Posted: Wed Feb 28, 2024 11:21 am
by darkfrei
Just sort table with special comparing function that compares square distances?

Re: Finding which object in the table is closest

Posted: Wed Feb 28, 2024 5:05 pm
by vilonis
You don’t even have to sort the list of enemies.

For each tower, walk through the list of all enemies (using a for loop), keeping track of the “closest enemy so far” and how far away from the tower they are. When you get to the end of the list, you know “closest so far” is actually the closest, period.

This isn’t a super efficient way to do it, but it is efficient enough to get started with, and vastly simpler than spatial partitioning approaches.

Re: Finding which object in the table is closest

Posted: Sat Mar 09, 2024 10:47 pm
by Azzla
What vilonis said. I happen to have a function for exactly this that I was using for an Auto-Battler prototype. For the purposes of library independence I added the relevant vector functions locally:

Code: Select all

local function len(x,y)
	return math.sqrt(x*x + y*y)
end
local function dist(x1,y1, x2,y2)
	return len(x1-x2, y1-y2)
end

--Returns the closest enemy within range of position vector (x,y).
function GetClosestInRange(enemies, position, range)
	local closest_enemy
	local closest_dist = 999999 --some arbitrarily large number

	for _,enemy in ipairs(enemies) do
		local distance = dist(position.x,position.y, enemy.x,enemy.y)
		if distance <= range then
			if distance < closest_dist then
				closest_dist = distance
				closest_enemy = enemy
			end
		end
	end
	return closest_enemy --nil if nothing is in range.
end
This is a context-independent way of handling it, but you could simplify/optimize this by making it a method of your tower and capturing the relevant variables. Something like:

Code: Select all

function Tower:getClosestEnemyInRange(enemies)
	if not self.target then self.closest_dist = 999999 end
	for _,enemy in ipairs(enemies) do
		local distance = dist(self.x,self.y, enemy.x,enemy.y)
		if distance < self.closest_dist then
			self.target = enemy
			self.closest_dist = distance
		end
	end
end

Re: Finding which object in the table is closest

Posted: Sun Mar 10, 2024 12:54 pm
by Trystan
Just to add a little bit more. If you want to do a lot of checks per frame and need it to be a little bit faster you don't need to take the square root for each object, since closer things will have a smaller distance squared than farther things. You can then take the square root of the winner at the end if you need actual distance.

Quick example..

Code: Select all

function getClosest(tower, enemies)
	local bestDist = math.huge
	local bestEnemy = nil
	local distSquared
	for _, e in ipairs(enemies) do
		distSquared = (tower.x - e.x) * (tower.x - e.x) + (tower.y - e.y) * (tower.y - e.y)
		if distSquared < bestDist then
			bestDist = distSquared
			bestEnemy = e
		end
	end
	-- return distance and enemy
  	-- if we need the actual distance we can square root it here
  	return math.sqrt(bestDist), bestEnemy
  end

Re: Finding which object in the table is closest

Posted: Sun Mar 10, 2024 8:25 pm
by RNavega
Azzla wrote: Sat Mar 09, 2024 10:47 pm

Code: Select all

	local closest_dist = 999999 --some arbitrarily large number
This reminded me, for this "absurdly large initial value" kind of thing, there's that constant that's loaded by default:

Code: Select all

math.huge
But my favorite is in Python: float("inf") creates a positive infinity value, nothing is above it.

Edit: it looks like math.huge is the same, if you print it, it says "inf".

Edit 2: an alias to math.huge is tonumber("inf")