Get closest enemy in 3D

Godot Version

4.7.2

Question

I’m curious about potential solutions how to find closest enemies to player in current scene.

currently player getting aim by clicking mouse on screen

func get_aim_direction() -> Vector3:
	var direction = get_mouse_direction()
	var direction_3d = Vector3(direction.x, 0.0, direction.y)
	var camera_rotation = get_viewport().get_camera_3d().global_rotation.y
	return direction_3d.rotated(Vector3.UP, camera_rotation)

the enemy are instantiated randomly

func _ready() -> void:
	for enemy in GlobalVars.get_enemy_count():
		var template = [MELEE_ENEMY, RANGED_ENEMY].pick_random()
		var new_enemy = template.instantiate() as Enemy
		all_enemies.append(new_enemy)
		
	var tween = create_tween()
	tween.tween_interval(2.5)
	for enemy in all_enemies:
		tween.tween_interval(1.0)
		tween.tween_callback(add_child.bind(enemy))
		enemy.defeat.connect(update_enemies.bind(enemy))
func _ready() -> void:
	global_position = NavigationServer3D.map_get_random_point(
		get_world_3d().navigation_map, 1, true
	)

Enemy is class, I could use group or maybe some other approach? What be the best func to use to determine closest enemy to player?

Put them in a group, get the group into an array, sort it by distance, and pick the first one. It’s two lines of code.

Many optimizations are possible but if the number of enemies is relatively small, you won’t need any.

this I know only how to do with loop.

func get_closest_enemy() -> Enemy:
	var enemy_array := get_tree().get_nodes_in_group("Enemy")

Yeah, you can search for the smallest distance in a loop or use Array::sort_custom() and provide a predicate lambda.

Would you mind share some code how would you solve this two line code?

I found this example in docs, but it’s a bit longer to solve it with two variable comparing.

func sort_ascending(a, b):
	if a[1] < b[1]:
		return true
	return false

func _ready():
	var my_items = [["Tomato", 5], ["Apple", 9], ["Rice", 4]]
	my_items.sort_custom(sort_ascending)
	print(my_items) # Prints [["Rice", 4], ["Tomato", 5], ["Apple", 9]]

	# Sort descending, using a lambda function.
	my_items.sort_custom(func(a, b): return a[1] > b[1])
	print(my_items) # Prints [["Apple", 9], ["Tomato", 5], ["Rice", 4]]
func get_nearest_node_in_group(group_name: String, origin: Vector3) -> Node3D:	
	var nodes := get_tree().get_nodes_in_group(group_name)
	nodes.sort_custom(func(a, b): return origin.distance_to(a.global_position) < origin.distance_to(b.global_position))
	return null if nodes.is_empty() else nodes[0]

I found small issue in testing

E 0:00:06:203   <anonymous lambda>: Invalid type in function 'distance_to' in base 'Vector3'. Cannot convert argument 1 from Object to Vector3.
  <GDScript Source>player.gd:79 @ <anonymous lambda>()
  <Stack Trace> player.gd:79 @ <anonymous lambda>()
                player.gd:79 @ get_nearest_node_in_group()
                player.gd:29 @ _unhandled_input()

It happens when only one enemy is in the scene. What would be guard to protect it ?

Those distance_to() calls should take positions. I updated the code above.

You can further improve the type safety of the whole thing by filtering the array so it eliminates eventual nodes that are not Node3D.