Find coding mistakes and help

Godot Version

‘4.4.1’

Question

Creating a 2d top down shooter but confused on how to create gun for character body 2d that shoots automatically at the nearest enemies.
Node structure for Player is the Normal structure for any character body 2d.

extends Sprite2D #Gun is taken as Sprite 2d and this is Gun.gd
@onready var player = $"." #player refrence looking weird idk why 
var enemies = {
	"Normal" = {health = 100 , speed = 200 , damage = 10} ,
	"Boss" = {health = 1000 , speed = 400 , damage = 100} ,
	"Giant" = {health = 2000 , speed = 500 , damage = 200}
}
var mobs = enemies
func _process(delta: float) -> void:
	var nearest_enemy = (enemies.global_position - player.global_position)
	if nearest_enemy.position >=  player.global_position:
		pass

Make a Node that has all enemies as children. Reference that node in your gun’s script (@export).

@export enemy_parent : Node

func get_closest_enemy() -> Node2D:
    var closest_enemy : Node2D = enemy_parent.get_child(0)
    for enemy in enemies:
        if distance_to(enemy) < distance_to(closest_enemy):
            closest_enemy = enemy

    return closest_enemy

(the functions might be called a little different)
(if your want to make it more efficient compare the distance squared)

I’m assuming that’s pseudocode…

I’d probably suggest something like:

extends Sprite2D

# Have a node somewhere that contains the enemy nodes, and reference it here.
@onready var Enemies = $"../Enemies"

const COOL_TIME:  float = 0.2 # 0.2 seconds
var gun_cooldown: float = 0.0

func _get_closest_mob() -> void:
    var dist_squared: float   = 100000.0 # "Infinity".
    var player_pos:   Vector2 = player.global_position
    var closest:      Node    = null

    for mob in Enemies.get_children():
        var dsq: float = player_pos.distance_squared_to(mob.global_position)
        if dsq < dist_squared:
            closest = mob
            dist_squared = dsq

    return closest # Note this can be null; there may be no enemies left...

func _process(delta: float) -> void:
    if gun_cooldown:
        gun_cooldown -= delta
    else:
        var mob = _get_closest_mob()
        if is_instance_valid(mob): # Check for null or being queue_free()d...
            _shoot(mob)
            gun_cooldown = COOL_TIME
3 Likes

what you want is an Area2D. it will detect collisions of your enemies or some Area2D that’s part of them.
when the collision happens, a target reference is assigned, and the gun aims in that direction.
if the target gets out of range, it gets nulled and a different collision must happen to assign a different one.

this solution is optimal for hundreds of enemies, as it will use the physics engine and godot can optimize it, it will also run in c++ instead of gdscript.

first, your gun should be its own node and you instantiate it as child of player, it does its own thing.
this will have an Area2D or ShapeCast2D to detect enemies. this example uses an Area2D but a ShapeCast would be better for line of sight, like an auto-aim.

extends Node2D

@onready var area_2d : Area2D = $Area2D#this is the reach of the gun

var target : Node2D#selected enemy

func _ready() -> void:
	area_2d.body_entered.connect(enemy_near)#connect the area2D. use area_entered if using areas.

func enemy_near(body) -> void:
	if not target:
		target = body#set target for firing at

func _physics_process(delta : float) -> void:
	if target:#weapon does nothing if there are no enemies
		if target.global_position.distance_to(global_position) > 400.0:#or the radius of your area2D
			#fire should be handled by a different node, the weapon just aims at them, a Node2D at the tip would fire when a button is pressed or if target is not null
			#you could also fire from here by calling a method in the weapon trigger
			#look at enemy #this would be done with some math
			pass
		else:
			#enemy is out of reach, null
			target = null

this is pseudo-code, I recommend you type it in the editor to fix any misspellings, and you need to aim at the position of the enemy, but you get the idea.

@export enemy_parent : Node

func get_closest_enemy() -> Node2D:
    var closest_enemy : Node2D = enemy_parent.get_child(0)
    for enemy in enemies:
        if distance_to(enemy) < distance_to(closest_enemy):
            closest_enemy = enemy

    return closest_enemy

Well this can work but how can you say the first child (0) will be the closest enemy always ?

I say this code is well working for child (0) but only if distance_to(enemy) < dist_to(closest_enemy) , what if distance_to(enemy) > dist_to_closest enemy.

And this is quite complex here is the diagram


That code doesn’t assume the first child is closest, it starts with the first child as a candidate for closest, and then loops over the rest checking if any are closer.

It’s not very efficient; it’s recalculating the distance to closest_enemy every loop iteration, and it does wind up with a redundant check at the beginning (since the for loop will start with child 0…) In the event that enemies is empty it’ll probably error on get_child(0).

But as long as the enemy list isn’t empty, this will tell you the closest one.

so you are saying it is Looping Inside Enemy and gets the very first child as the nearest enemy everytime.

btw leave it , I am asking why you and even other coders are using distance square. When I asked ai it said it is more efficient.

The equation for distance in 2d is:

sqrt((dx * dx) + (dy * dy))

where dx is the difference (or “delta”) between the x coordinates and dy is the same for the y coordinates.

Distance is a magnitude; it’s always positive. If we have two positive numbers (let’s say A and B) and we know A > B, then we also know sqrt(A) > sqrt(B). The difference between them will change, but if all we care is “which one is bigger?” or “which one is smaller?”, then the square root doesn’t change that relationship.

Square roots are expensive, in CPU use terms.

On the other hand, if we calculate distance squared, the equation is:

(dx * dx) + (dy * dy)

Without that expensive square root. Since we’re just looking for which distance is smallest, we can use the cheaper-to-calculate squared values and get the same result with less math.

This same trick works (for similar reasons) if you’re trying to check whether something is within a certain distance (say, for collision or aggro or something). You can check if the squared distance is inside the squared range, and will get the same results as if you had checked distance within range, but the CPU does less work to get you there.

No, look at what the code does:

func get_closest_enemy() -> Node2D:
    # This line initializes `closest_enemy` with the first child.
    var closest_enemy : Node2D = enemy_parent.get_child(0)

    # This loop iterates over the list of enemies.
    for enemy in enemies:
        # If the enemy in this loop iteration is closer than `closest_enemy`...
        if distance_to(enemy) < distance_to(closest_enemy):
            # ...then replace `closest_enemy` with our new closer one.
            closest_enemy = enemy

    # The loop is done, `closest_enemy` should be whichever one the loop picked.
    return closest_enemy
2 Likes