How to make the cloned scenes not overlap

Godot Version

4.3

Question

I am making a top down 2D game. I have a code that spawns trees on launch, but most of the time they overlap. I want a way to have the trees not appear on each other, so it looks nice and the player is able to move smoothly

Share this code please.
Use triple backticks ``` around your code snippet.

Here it is

	for i in 100:
		var clone := preload("res://Scenes/tree.tscn").instantiate()
		var x: float = randf_range(-2500, 2500)
		var y: float = randf_range(-2500, 2500)
		clone.position = Vector2(x, y)
		add_child(clone)

It’s not that easy as it may seem.
Here’s my attempt at it.

This is the scene tree of the “tree.tscn” - I added the Area2D node that stretches over the entire tree.

And here’s the spawning code:

extends Node2D


var trees: Array
var buffer_time: float = 1.0


func _ready() -> void:
	for i in 100:
		var tree := preload("res://cloning/tree.tscn").instantiate()
		_move_to_random_position(tree)
		add_child(tree)
		
		tree.get_node("Area2D").body_entered.connect(_move_to_random_position)
		trees.append(tree)
	
	await get_tree().create_timer(buffer_time).timeout
	for tree in trees:
		tree.get_node("Area2D").body_entered.disconnect(_move_to_random_position)
	
	trees.clear()


func _get_random_position() -> Vector2:
	var x: float = randf_range(-2500, 2500)
	var y: float = randf_range(-2500, 2500)
	return Vector2(x, y)


func _move_to_random_position(node: Node2D) -> void:
	node.position = _get_random_position()
	print("Moved node %s to %s" % [node.name, node.position])

What it does is it first assigns a random position to each tree like you had before, then connects to the Area2D’s body_entered signal to detect if another tree has been spawned at the same position. If yes - it would trigger the signal and move the tree to a different position. This should repeat until all the trees are not overlapping anymore.
The buffer_time is used to allow it to settle after some time and disconnect all signals. Otherwise in some cases it could loop forever if you have a lot of overlap.

Let me know if there is anything unclear about this, or you have any issues.

For each tree, save the position you picked for it as a “last_picked_position” or such, then for each new random position check if “position” is too similar to “last_picked_position” with a while loop.
If you have several trees I guess you could have a last_picked_position1, last_picked_position2 etc for the number you need. Set all too null, then when selecting a position “if last_picked_position1” == null, set the first instance to that. If last_picked_position1 != null, go forward to last_picked_position2 etc. That in combination with comparing the random position to the new should make it possible to spawn many trees without having any of them overlap another.

Here is a possible solution in code. I made it by editing some lines I have for a similar thing and just kept my variable names and values. Just to give you a clearer idea of what I mean. I guess it would be better to keep all the previous positions in an array and compare the new position with each item in the array. If you need some help, let me know but it is late here and I am heading to sleep.

	var random_x_position = randi_range(0, end)
	if last_position == null:
		while random_x_position >= last_position - 100 and random_x_position <= last_position + 100:
			random_x_position = randi_range(0, end)
			print("rerolled")
		last_position = random_x_position
		return_block_instance.position = Vector2(random_x_position, 0)
	elif last_position2 == null:
		while random_x_position >= last_position - 100 and random_x_position <= last_position + 100:
			random_x_position = randi_range(0, end)
		print("rerolled")
		last_position2 = random_x_position
		return_block_instance.position = Vector2(random_x_position, 0)
1 Like

Here is a script you can use. Just be aware that it is not great for performance if you want to add large numbers of things, as it checks the positions of every new thing it places against every previously placed things position. If you try to make it spawn too many things, it will get stuck in an endless process of trying to find an empty spot on the screen.

You can add the different pngs or scenes into the array in the inspector. Also the variable number_of_objects_to_place is the number of trees or whatever you want to spawn.

extends Node2D

@export var objects: Array
@export var number_of_objects_to_place: int
@export var position_offset: Vector2

var previously_picked_positions: Array
var placement_area
var random_object: int

func _ready() -> void:
	placement_area = get_viewport().get_visible_rect().size
	randomize_positions()
	
	
func randomize_positions():
	for i in number_of_objects_to_place:
		var random_position : Vector2
		
		var valid = false
		while valid == false:
			valid = true
			random_position = Vector2(randi_range(0, placement_area.x),randi_range(0, placement_area.y))
			
			for prev_pos in previously_picked_positions:
				if prev_pos != null and random_position.distance_to(prev_pos) < position_offset.length():
					valid = false
					break
		
		previously_picked_positions.append(random_position)
		instantiate_and_place(random_position)


func instantiate_and_place(random_position: Vector2):
	random_object = randi_range(0, objects.size() - 1)
	if objects[random_object] is CompressedTexture2D:
		print("compressedtext")
		var sprite := Sprite2D.new()
		sprite.texture = objects[random_object]
		sprite.position = random_position
		add_child(sprite)
	elif objects[random_object] is PackedScene:
		print("packedscene")
		var instance = objects[random_object].instantiate()
		instance.position = random_position
		add_child(instance)

i this:
https://forum.godotengine.org/t/how-can-i-generate-random-positions-without-them-being-too-close-to-each-other/119206?u=samthead
what you want?

A common approach in procedurally generated games is to find local maximas or peaks in your noise data. Currently you are using randf_range many times, but using a FastNoiseLite allows you to sample noise.

Here’s a slide deck on finding peaks in 1d and 2d datasets.

The idea is given a noise texture, like the one below; we sample any point on it, is that point brighter than it’s neighbors? If so, we place a tree.

In Godot I’ve plotted coins on top of local maximas, they are fairly uniformly distributed. Though I can’t control exactly how many are spawned, editing the noise’s frequency can create a dense forest, or sparse plains. Similarly the trees will be at least SAMPLE_STEP pixels away from each other.

This was generated using a very naive approach that should be optimized for much larger scenes, but even with your 5000 square range it’s quick enough for one generation.

@export var noise: Noise

const SAMPLE_STEP = 12.0
const MIN_SIZE = 0
const MAX_SIZE = 5000

func _ready() -> void:
	for y in range(MIN_SIZE, MAX_SIZE, SAMPLE_STEP):
		for x in range(MIN_SIZE, MAX_SIZE, SAMPLE_STEP):
			var sample: float = noise.get_noise_2d(x, y)
			if sample < 0.01: # skip low-value samples, set higher for clumping trees
				continue

			var neighbor_top: float = noise.get_noise_2d(x, y - SAMPLE_STEP)
			var neighbor_bot: float = noise.get_noise_2d(x, y + SAMPLE_STEP)
			var neighbor_left: float = noise.get_noise_2d(x - SAMPLE_STEP, y)
			var neighbor_right: float = noise.get_noise_2d(x + SAMPLE_STEP, y)
			if sample > neighbor_top and\
				sample > neighbor_bot and\
				sample > neighbor_left and\
				sample > neighbor_right:
				make_coin(Vector2(x,y))
2 Likes

This looks like a nice solution. I just need to know how to change the make_coin(Vector2(x,y)) to spawn trees. Do I just put the very previous code in a new function called something like “spawn trees” and then replace the make_coin with it?

I am a very beginner. The only game in Godot I made was a simple clicker

Take the code you had before but without the head of the for-loop:

var clone := preload("res://Scenes/tree.tscn").instantiate()
var x: float = randf_range(-2500, 2500)
var y: float = randf_range(-2500, 2500)
clone.position = Vector2(x, y)
add_child(clone)

And put it into a new function, for example spawn_tree(x: int, y: int) -> void which you can replace make_coin with.

# mostly cloned from @TopazowyGolem's code

func spawn_tree(x: int, y: int) -> void:
	var clone := preload("res://Scenes/tree.tscn").instantiate()
	var x: float = randf_range(-2500, 2500)
	var y: float = randf_range(-2500, 2500)
	clone.position = Vector2(x, y)
	add_child(clone)
# mostly cloned from @gertkeno's code

@export var noise: Noise

const SAMPLE_STEP = 12.0
const MIN_SIZE = 0
const MAX_SIZE = 5000

func _ready() -> void:
	for y in range(MIN_SIZE, MAX_SIZE, SAMPLE_STEP):
		for x in range(MIN_SIZE, MAX_SIZE, SAMPLE_STEP):
			var sample: float = noise.get_noise_2d(x, y)
			if sample < 0.01: # skip low-value samples, set higher for clumping trees
				continue

			var neighbor_top: float = noise.get_noise_2d(x, y - SAMPLE_STEP)
			var neighbor_bot: float = noise.get_noise_2d(x, y + SAMPLE_STEP)
			var neighbor_left: float = noise.get_noise_2d(x - SAMPLE_STEP, y)
			var neighbor_right: float = noise.get_noise_2d(x + SAMPLE_STEP, y)
			if sample > neighbor_top and\
				sample > neighbor_bot and\
				sample > neighbor_left and\
				sample > neighbor_right:
				spawn_tree(x, y)

That kinda works, but now trees are filling the whole screen with no space left

Make sure to assign a Noise to the export. Increase your SAMPLE_STEP if your trees are larger than 12 pixels

1 Like

Yeah, it works now. Thanks

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