Hey guys. Im a beginner and Im kind of stuck with a problem my brain can't handle. Im currently working on a tower defense. I spawn minions into an enemy group, for each enemy in the group I check if the target is in tower range. If yes it returns the enemy if not its nulled. (I kind of got the process from another TD I found online)

Thats the chose target function:

`

func choose_target():
	var pos = get_global_position()
	for enemy in get_tree().get_nodes_in_group("enemy"):
			if pos.distance_to(enemy.get_global_position()) <= fire_range && enemy.get_health() > 0:
				target = enemy
			else:
				target = null

	return target

`

Now to my problem: Since the tower checkes randomly for each enemy the damage is all over the place, Ofc the most logical thing is attack the closest minion and lock on it till its dead. I made it work with attacking the closest with a local variable that saves the last attack distance and compares it to the new one. However I can't figure out how to lock on a target. I think my biggest problem is the "for enemy in" loop. And since I kind of tried options to the best of my knowledge for a couple of hours now Im kind of lost how to make this work.

Any ideas?

Much appreciated!

To be honest, I'm a little confused at the question. Is the problem you need to tower to continually attack the closest enemy, or the problem you need the tower to follow what it finds as the closest enemy until a new enemy takes its place?

Having done both before for a tower defense tutorial (sadly removed due to Patreon issues), I'll do my best to answer.


For always attacking the closest enemy you pretty much have everything you need. You just need to compare the distance of the enemy you are currently tracking against all of the new enemies. Using the code above, you just need to make a few minor changes:

# NOTE: I am assuming the target variable is the enemy the tower is/was attacking...
func choose_target():
	var pos = get_global_position()
	var closest_distance = 999999;
	var closest_target = null;
	
	if (target != null):
		closest_distance = (pos - target.get_global_position).length()

	for enemy in get_tree().get_nodes_in_group("enemy"):
		if (enemy.get_health() > 0):
			var distance_to_enemy = (pos - enemy.get_global_position).length();
			if (distance_to_enemy < closest_distance and closest_distance <= fire_range):
				closest_target = enemy;
				closest_distance = distance_to_enemy;
	return closest_target;

I made a few changes.

Functionally, I changed how the choose_target function works. Now it returns the closest_target variable instead of directly setting target to the closest target or to null. This is mostly just a style thing, but assuming that target is a class variable intended to be used for the current enemy the tower is tracking, you probably do not want to change it directly in the function and return it, unless target is not for the currently tracked enemy, in which case feel free to change the code above. Anyway, to get the same functionality as before with the slightly changed function, you just need to assign target to the result of the choose_target function.

The second thing I changed is how targets are compared. The previous version just checked whether a target was in range, and didn't compare distances from the tower to the target. This means the tower will always aim at the last enemy in the enemy group that is within the tower's range. Now the choose_target function should always return the closest enemy to the tower that is within the tower's range, or null if there are no enemies in range.

Using this, you just need to call choose_target every so often, I would suggest on a timer, if you want the tower to always aim at the closest enemy.

Keep in mind, that this is not the most effective way to check for enemies, as you are going through every enemy within the group. A better way would be to make a Area or Area2D node whose collision shape is covers the tower's range, that way you then only need to check every enemy within the Area, not every enemy in the game. If you run into performance issues, I would try using a Area or Area2D node and see if that helps :smile:


As for tracking the same enemy until it is dead, this is even easier depending on how you have everything setup. Since you have a get_health function, all you should need to do is add something like this before you fire at the enemy:

if (target.get_health() <= 0):
	target = choose_target();
if (target != null):
	# Do whatever you need to fire at the enemy here!

Granted this will not work if you are calling queue_free as soon as the enemy's health reaches zero. In that case, I would suggest adding code like the following to the enemy's script:

var towers_watching_enemy = []
# I'm assuming you have a function to damage the enemy. For this example, I'll call it 'damage'
func damage(amount):
	health -= amount;
	if (health <= 0):
		for tower in towers_watching_enemy:
			tower.target = null;
		queue_free();

Then you'll need to make a minor adjustment to the towers when you get a new target:

# Once you have determined you need a new target and are going to call choose_target...
#
# NOTE: I think there is a function in lists called 'contains' or something like that for
# checking whether an item is within a list. You may want to check this before removing
# the tower from the list to ensure the tower is actually within the list.
target.towers_watching_enemy.remove(self);
target = choose_target();
target.towers_watching_enemy.add(self);

Then, in theory, the towers will track the same enemy until it is destroyed.


Hopefully this helps! :smile:

Holy ship! Thanks for the in depth answer.

Indeed I was looking for tracking an enemy until dead / out of range and I think I understand the concept. (the queue_free solve is great, that was an error i was struggling with when testing with multiple towers killing a target)

This helps me a lot!

@R3ZOfficial said: Holy ship! Thanks for the in depth answer.

Indeed I was looking for tracking an enemy until dead / out of range and I think I understand the concept. (the queue_free solve is great, that was an error i was struggling with when testing with multiple towers killing a target)

This helps me a lot!

I'm glad I was able to help :smile:

(As for the queue_free stuff, it took a couple days of trying different solutions until I landed on something that sorta worked :smile: )

4 years later