A better way to avoid randomly spawning enemies on each other

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Baolnar

Hi All,

My second question. In my 3d rpg game, I am creating a prototype where you have two knights and a bunch of randomly placed hounds in a room.

I have created an Area node in the main scene that will be the area where the randomly created hounds will appear.

In a script attached to the main scene, loop 6 times and create hound instances off of my hound scene and add then as CHILDs to the Area node. I do this because it is, at least in my opinion, cleaner to generate random x and z positions for grounded enemies by simply using the range -1,1 for the x and z coordinates. These are taken as local coordinates within the area so works nicely. Each hounds spawn function is nicely contained within an enemy.gd script attached to each Hound scene.

Because the hounds are children of this area that I created and this is scalled, I reset all of the hounds scale by mutliplying it with 1/Area.scale.x, 1/Area.scale.y and 1/Area.scale.z (found this useful trick out in another Godot forum)

In order to detect whether a hound has landed on another hound while being randomly placed, I have each hound have an Area node of its own that is roughly the same size as its collision shape. In the _physics_process(delta)() function of the hound, I have this check to see if it is spawned on another area:

extends KinematicBody
var spawned = false
signal spawn_collision

func _physics_process(delta):
var bodies = $Area.get_overlapping_areas()
if(bodies.size()):# and !spawned):
	if(!spawned):
		spawn()
	else:
		spawned = true

func spawn():
randomize()
var x_pos = rand_range(-1,1)
var z_pos = rand_range(-1,1)
set_translation(Vector3(x_pos,0,z_pos))

This means, if the hounds did land on each other, for a split second when the level loads up, I see a hound or two quickly change positions before settling down.

The main level scene script takes care of spawning all hounds and when all hounds have their spawned flag set to true, it will stop spawning and move on to other processing and not call the spawning process again.

It does appear to be quite a round about way of accomplishing something trivial. Is there a more effecient way of detecting collisions straight away of a spawned object? (without calculating the bounds of each spawned enemy already and comparing it with this list)

:bust_in_silhouette: Reply From: Baolnar

After spending some time on this, I have a better way than what I explained in my question.

  1. I create a new scene called EnemySpawner.
  2. The Scene simply contains a MeshInstance Node as its root node.
  3. I rename this root node to EnemySpawner.
  4. I attach a script to this root node called "EnemySpawner.gd.
  5. I choose a BoxShape for the Meshinstance.
  6. I can change the size and position of this if I want - practically this will be done more when I place this in my main level scene.
  7. In the EnemySpawner.gd script ready function, I calculate the minimum and maximum x and z positions (dealing only with grounded units for the time being so y is 0).
    This is done using the code:
var min_x = 0
var max_x = 0
var min_y = 0
var max_y = 0
var min_z = 0
var max_z = 0

func _ready():
    # Called when the node is added to the scene for the first time.
    # Initialization here
    min_x = translation.x - ((mesh.size.x * scale.x)/2)
    max_x = translation.x + ((mesh.size.x * scale.x)/2)
    min_z = translation.z - ((mesh.size.z * scale.z)/2)
    max_z = translation.z + ((mesh.size.z * scale.z)/2)
    pass
  1. I have a function called spawn() in the EnemySpawner.gd script that takes the enemy object to spawn as an argument and sets its translation (currently I am not using random atm, but that can easily be added soon):

func spawn(enemy):
var enemy_x_offset = enemy.get_node(“EnemyCollider”).shape.get_extents().x/2
var enemy_z_offset = enemy.get_node(“EnemyCollider”).shape.get_extents().z/2

enemy.translation = Vector3(translation.x,0,max_z - enemy_z_offset)

The important thing to note is that I also have to subtract or add the enemy collider offset to the position I am inserting the object in otherwise, when placed on an edge, the enemy will be sticking out of the spawn area.

Finally, in my main level script, I can create random enemies using the spawn area. I do not add them as childs to the EnemySpawner but instead add them as child nodes to the main level to avoid child node transform headaches:

func _ready():
var hound_scene = load(“res://Hound.tscn”)
var hound_node = hound_scene.instance()
hound_node.set_name(“Hound1”)
$EnemySpawner.spawn(hound_node)
add_child(hound_node)

Though I mentioned in my question I didnt want to calculate the points to prevent enemies spawning on each other - I have come to the conclusion (at least for now) that it is better to do so. This way it is easier as the logic will be encapsulated within the EnemySpawner scene. I can have an Array of bounds that have already been spawned so new enemies cannot be spawned within those bounds.

I also liked this approach because when I drag the EnemySpawner to my level, I can adjust its size and position to what I want it to be and the spawned enemies will continue to spawn correctly.