Please need help. Problem with tree procedural generation

Godot Version

v4.4.1.stable.official [49a5bc7b6]

Question

I want to create trees by using procedural generation and create only where is my grass tiles but i don't understand what i am doing wrong but my trees are createing mostly outside of the world and not even at grass tiles, i did be happy to hear any ideas or help!!


here is my code

@tool
extends Node2D

@export_tool_button("Test") var generate: Callable = generate_world
@export var noise_tree_text : NoiseTexture2D
@export var noise: Noise
@export var tree_noise : Noise 
@export var tree_scene: PackedScene

@onready var tilemap = $Ground
@onready var trees_node: Node2D = $Trees


var width: int = 100
var height: int = 100

func _ready():
	tree_noise = noise_tree_text.noise
	generate_world()
		
const GRASS_THRESHOLD = 0.0
const SAND_THRESHOLD = 0.2
const TILE_SIZE: int = 32

var water_cells
var sand_cells
var grass_cells
func generate_world():
	noise.seed = randi()
	water_cells = []
	sand_cells = []
	grass_cells = []
	for x in range(-width/2, width/2):
		for y in range(-height/2, height/2):
			var noise_val = noise.get_noise_2d(x, y)
			var tree_noise_val = tree_noise.get_noise_2d(x, y)
			
			if noise_val < GRASS_THRESHOLD:
				grass_cells.append(Vector2i(x, y))
			if noise_val < SAND_THRESHOLD:
				sand_cells.append(Vector2i(x, y))
			water_cells.append(Vector2i(x, y))

	tilemap.clear()
	tilemap.set_cells_terrain_connect(water_cells, 0, 2) # Water Cells
	tilemap.set_cells_terrain_connect(sand_cells, 0, 1) # Sand Cells
	tilemap.set_cells_terrain_connect(grass_cells, 0, 0) # Grass Cells
	
	if trees_node:
		trees_node.queue_free()
		
	trees_node = Node2D.new()
	trees_node.name = "Trees"
	add_child(trees_node)
	
	for cell in grass_cells:
		var tree_val = tree_noise.get_noise_2d(cell.x, cell.y)
		if tree_val > 0.4:
			var tree_instance = tree_scene.instantiate()
			tree_instance.position = Vector2(cell.x, cell.y) * TILE_SIZE
			trees_node.add_child(tree_instance)
	

The first thing I’d suggest is using .local_to_map() and .map_to_local() on TileMapLayer rather than hand-rolling your positions by multiplying in the tile size.

Honestly, it looks to me like your tile scaling for the trees is out by a factor of 2. That bite out of the left side chunk of forest is a pretty good match for the little chunk of sand on the left side of the map, if you scaled the tree positions down by half they look like they’d be in the right place.

I bet if you just make TILE_SIZE = 16 it will look right, but my advice would be to use .map_to_local() for tree positioning.

1 Like

Yeah! It is now working better then before but still there is many wrong parts


I changed from 32 to 16 and used map_to_local
there is the code parts

func _ready():
	tree_noise = noise_tree_text.noise
	generate_world()
		
const GRASS_THRESHOLD = 0.0
const SAND_THRESHOLD = 0.2
const TILE_SIZE: int = 16

for cell in grass_cells:
		var tree_val = tree_noise.get_noise_2d(cell.x, cell.y)
		if tree_val > 0.4:
			var tree_instance = tree_scene.instantiate()
			tree_instance.position = tilemap.map_to_local(cell)
			trees_node.add_child(tree_instance)

to be sure i don’t understand what have changed when i changed 32 to 16 and also there is some trees outside map so idk how to fix it

As far as I can see, TILE_SIZE was only used in the tree placement logic, so now you’ve stopped using it, you can probably get rid of TILE_SIZE entirely.

It looks to me like somehow your tree generation is happening twice; you have what appear to be two copies of your trees, one somewhat down and right. Is that just what your tree scene looks like?

I wonder if your tree scene is offset from the origin a bit, so when you place it at a position it actually draws offset from that.

Yes my tree scene is looking like this, also i changed every trees offset to have that sprite Y ordering( when player is besides or in front of the tree)

Try moving the trees closer to the origin in your scene…

1 Like

you meant like this ?


also with this it looks like this

It looks like your trees are in the right place now, but your tilemap is offcenter. Either you need to horizontally center the tilemap, or you need to compensate in the tree positions.

Mmm yes my tilemaplayer was offcenter too :sweat_smile: now it looks like this


, also i added randf() to lower my trees spawn rate but its not working

for cell in grass_cells:
		var tree_val = tree_noise.get_noise_2d(cell.x, cell.y)
		if tree_val > 0.4 and randf() < 0.1:
			var tree_instance = tree_scene.instantiate()
			tree_instance.position = tilemap.map_to_local(cell)
			trees_node.add_child(tree_instance)

UPDATE: I make some changes in noise and it stars looking more as i want but still it looks not good the trees are overlaping each others and some are going oustide of the world.

also i think the problem is that in my trees scene where i have 2 trees and they are at same positions 0,0 they already overlap each other but idk how to separate them and write code for separted trees.I mean in this code i just spawn trees and there are already 2 trees there but with separete idk how to manage it

i would suggest to just use random values for the trees instead of noise. This tutorial implements something similar:

	for cell in grass_cells:
		var tree_val = randf()
		if randf < 0.01:
			var tree_instance = tree_scene.instantiate()
			tree_instance.position = tilemap.map_to_local(cell)
			trees_node.add_child(tree_instance)

And you probably didnt know this but you can create a “scene collection” inside your tileset, which allows you to place scenes as tiles. Your trees for example

Hmm yes i didn’t know about that, so you suggest to add scenes as tiles will be that easier? also i created smth like this, it was working and generating trees(not ideal) but idk WHY but without anything now it giving errors, i suddenly closed godot without saving it but when i open it again everything was same so idk.

@tool
extends Node2D

@export_tool_button("Test") var generate: Callable = generate_world
@export var noise_tree_text : NoiseTexture2D
@export var noise: Noise
@export var tree_noise : Noise 
@export var tree_oak_scene: PackedScene
@export var tree_pine_scene: PackedScene

@onready var tilemap = $Ground
@onready var tree_oak_node: Node2D = $Treeoak
@onready var tree_pine_node: Node2D = $Treepine

var width: int = 100
var height: int = 100

func _ready():
	tree_noise = noise_tree_text.noise
	generate_world()
		
const GRASS_THRESHOLD = 0.0
const SAND_THRESHOLD = 0.2
const TILE_SIZE: int = 16

var water_cells
var sand_cells
var grass_cells
func generate_world():
	noise.seed = randi()
	water_cells = []
	sand_cells = []
	grass_cells = []
	for x in range(-width/2, width/2):
		for y in range(-height/2, height/2):
			var noise_val = noise.get_noise_2d(x, y)
			var tree_noise_val = tree_noise.get_noise_2d(x, y)
			
			if noise_val < GRASS_THRESHOLD:
				grass_cells.append(Vector2i(x, y))
			if noise_val < SAND_THRESHOLD:
				sand_cells.append(Vector2i(x, y))
			water_cells.append(Vector2i(x, y))

	tilemap.clear()
	tilemap.set_cells_terrain_connect(water_cells, 0, 2) # Water Cells
	tilemap.set_cells_terrain_connect(sand_cells, 0, 1) # Sand Cells
	tilemap.set_cells_terrain_connect(grass_cells, 0, 0) # Grass Cells
	
	if tree_oak_node:
		tree_oak_node.queue_free()
	elif tree_pine_node:
		tree_pine_node.queue_free()
		
	tree_oak_node = Node2D.new()
	add_child(tree_oak_node)
	
	for cell in grass_cells:
		if randf() < 0.25:
			var tree
			var is_oak := randf() < 0.6

			if is_oak:
				tree = tree_oak_scene.instantiate()
			else:
				tree = tree_pine_scene.instantiate()

			tree.position = tilemap.map_to_local(cell)

			if is_oak:
				tree_oak_node.add_child(tree)
			else:
				tree_pine_node.add_child(tree)

				

ERROR: res://Scripts/game.gd:73 - Cannot call method ‘add_child’ on a null value.
Set tree_pine_scene
Set tree_oak_scene
ERROR: Unrecognized UID: “uid://dxobtx5o07hri”.
ERROR: Unrecognized UID: “uid://bp6wru1jpe2bu”.
Set tree_oak_scene
Set tree_pine_scene
ERROR: res://Scripts/game.gd:73 - Cannot call method ‘add_child’ on a null value.

UPDATE: okay i fixed this i don’t understand why but everytime i close the project my 2 tree scenes are being deleted from Game scene…

There is this with this code

I think this is because you cant add nodes like this in the editor. This only works in game. This is looking better now, but some trees are on the water and on the sand.

The trees also look kinda large in comparison to the lighthouse

yes some trees are on water and sand and also trees are bigger from Ligh house becuase the scale was 1.5 i fixed that but i don’t understand why i can’t control the trees density i mean they spawn in every place, i am using ranf() but it did’t help

UPDATE: I managed to create less trees by changing little bit the code but the tree scenes are being deleted for my main gam scene and i am getting this errors



also i changed this in the code

if tree_noise_val > 0.5 and randf() < 0.1:

I also changed this now, but it seems not to helping too much because i am getting same errors but my trees are not being deleted.


	if tree_oak_node:
		tree_oak_node.queue_free()
	if tree_pine_node:
		tree_pine_node.queue_free()
		
	tree_oak_node = Node2D.new()
	tree_oak_node.name = "Treeoak"
	add_child(tree_oak_node)
	tree_oak_node.owner = get_tree().edited_scene_root
	
	tree_pine_node = Node2D.new()
	tree_pine_node.name = "Treepine"
	add_child(tree_pine_node)
	tree_pine_node.owner = get_tree().edited_scene_root
	

E 0:00:03:445 game.gd:13 @ @implicit_ready(): Node not found: “Treepine” (relative to “/root/Game”).
<C++ Error> Method/function failed. Returning: nullptr
<C++ Source> scene/main/node.cpp:1877 @ get_node()
game.gd:13 @ @implicit_ready()

also i want to ask is it better to add my tree scenes as tiles? but can they be used to be cutted for example ? I added them as node2d because i was thinking i can’t cut trees as tiles.

They are still the tree-scenes when you use them as tiles and can therefore also have functionality like being cut down.
I think one big problem is you creating these parent-nodes in code and trying to run it in the editor. In this case it would be better to use the trees as tiles