Help with Preventing Enemies from Spawning on Top of Each Other

What am I trying to do?
I’m working on a 2D game where I have two types of enemies:

  • Enemy Type 1: Moves in a straight line.
  • Enemy Type 2: Tracks the player.

My goal is to ensure Enemy Type 2 does not spawn on top of Enemy Type 1 (or other Enemy Type 2 instances).
The enemies are spawned randomly using a SpawnManager. I’ve set up Area2D nodes for both enemies to help detect overlaps.

Node Setup:
Each enemy node has:

  • MeshInstance2D
  • CollisionShape2D
  • Area2D with a CollisionShape2D as a child.
    The player node also has an Area2D.

Here’s my original SpawnManager logic for spawning enemies:

func spawn_enemy(enemy_type: String) -> void:
	# Only spawn if max hasn't been reached
	if enemy_type == "type1" and current_enemies_type1 < max_enemies_type1:
		var enemy_instance = enemy_type1_scene.instantiate()
		add_child(enemy_instance)
		enemy_instance.position = spawn_points[randi() % spawn_points.size()]
		current_enemies_type1 += 1
	elif enemy_type == "type2" and current_enemies_type2 < max_enemies_type2:
		var enemy_instance = enemy_type2_scene.instantiate()
		add_child(enemy_instance)
		enemy_instance.position = spawn_points[randi() % spawn_points.size()]
		current_enemies_type2 += 1

Here’s the original enemy_2 script:

extends CharacterBody2D

@export var speed = 50  # Movement speed

var player = null  # Reference to the player node

func _ready() -> void:
	player = get_node("/root/world/player")

func _physics_process(delta: float) -> void:
	if player:
		var direction = (player.position - position).normalized()
		position += direction * speed * delta

What is the expected result?
I expect Enemy Type 2 to avoid spawning on top of Enemy Type 1.

What is happening instead?
Enemy Type 2 spawns on top of Enemy Type 1

What have I tried so far?

  • Used area_entered and area_exited signals to track overlaps, but the results are inconsistent.
  • Verified collision layers and masks:
  • Enemy Type 1: Collision Layer 1
  • Enemy Type 2: Collision Layer 2, Mask detects Layer 1 and Layer 2.
  • Adjusted spawn validation to wait for signals (await get_tree().process_frame).

What am I missing here? Is there a better way to ensure proper spacing between enemies during spawning? Any help would be greatly appreciated!

1 Like

I can think in two approachs for this:

Using Area2D:

In your main code:

func _validate_position(p_pos: Vector2) -> bool:
	your_area_node.global_position = p_pos
	# Important, in your example you awaited for process_frame, but for
	# for physics related things that is useless, the correct signal is this one
	await get_tree().physics_frame
	
	# Detected something? Means already has enemies, return false
	if your_area_node.get_overlapping_bodies() > 0:
		return false

	# Nothing detected, the position is free, return true
	else:
		return true

Checking distance:

In all your enemies, both 1 and 2 you need to add them to a group, put that on their scripts on the ready function:

func _ready() -> void:
	add_to_group("enemy")

In your main code:

func _validate_position(p_pos: Vector2) -> bool:
	var is_valid_position := true
	# Get all nodes inside enemy group and iterate into this array
	for enemy in get_tree().get_nodes_in_group("enemy"):
		# The distance value to use will depends of the size from your
		# enemies, try different values and find what fits more
		if enemy.global_position.distance_to(p_pos) < 20:
			is_valid_position = false
			break
	
	return is_valid_position




And in your spawn code:

3 Likes

Thank you, sir! Using the distance approach fixed my issue.

I scaled out the number enemy 2’s and increased the interval speed for it’s spawn and faced no issue after a few different tries.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.