Trouble with seed not working here

Godot Version

4.3

Question

I have this script that is responsible for setting the seed for random node map generation

And here is my script responsible for creating said node map. Im not gonna go into detail about how it works cause that would take too long but basically it uses a lot of randi_range() to create random node placements

extends Node2D

@export var maxNodes : int = 20
@export var minDistance : float = 150.0
@export var maxConnectionDistance : float = 400.0  # Max distance between connected nodes (in pixels)
@export var maxShopNodes : int = 2
@export var maxRandomNodes : int = 4
@export var encounterScene : PackedScene
@export var bossStage : PackedScene 
var nodeCount : int = 1
var nodes : Array = []
var newParentNodes : Array = []
var queuedChildren = []
@export var mapProps : Array = []
var nodeScene = preload("res://Scenes/StageMaps/map_node.tscn")
@export var unqiueEnemies : Array[Dictionary] = [
{"Scene": preload("res://Scenes/Enemies/Slime.tscn"), "reqLvl": 0},
{"Scene": preload("res://Scenes/Enemies/Beetle.tscn"), "reqLvl": 2},
{"Scene": preload("res://Scenes/Enemies/JellyFish.tscn"), "reqLvl": 4},
{"Scene": preload("res://Scenes/Enemies/StoneGolem.tscn"), "reqLvl": 6},
{"Scene": preload("res://Scenes/Enemies/Wolf.tscn"), "reqLvl": 8},
]

var nodeCreationTimer : Timer

var rootNode : Node2D = null
var bossNode : Node2D = null

@export var seedName := ""
var seedToUse : int

func _ready() -> void:
	seed(PlayerSingleton.currentSeed)
	
	if PlayerSingleton.finishedTutorial == true:
		Dialogic.start("stageMapTutorial2")
	elif PlayerSingleton.tutorial == true:
		Dialogic.start("stageMapTutorial")
	
	$"../Hud/TextureRect/Coins".text = "Coins : " + str(PlayerSingleton.coinCount)
	
	PlayerSingleton.unqiueEnemies = unqiueEnemies.duplicate()
	
	rootNode = nodeScene.instantiate()
	rootNode.active = true
	rootNode.paths = []
	rootNode.children = []
	rootNode.mapIndex = 1
	rootNode.connectionCount = 0  # Initialize connection count for the root node
	rootNode.maxConnections = 3  # Root node can connect up to 3 nodes
	rootNode.scene = encounterScene
	rootNode.modulate.a = 255
	add_child(rootNode)
	
	nodes.append({"Scene": rootNode, "Index": rootNode.mapIndex, "pos": rootNode.position})
	
	newParentNodes.append(rootNode)
	
	nodeCreationTimer = Timer.new()
	nodeCreationTimer.wait_time = 0.01
	nodeCreationTimer.one_shot = false
	nodeCreationTimer.connect("timeout", _on_TimerTimeout)
	add_child(nodeCreationTimer)
	nodeCreationTimer.start()
	
	await get_tree().create_timer(1).timeout
	
	if PlayerSingleton.currentNodeIndex != 0:
		for dicts in nodes:
			if dicts["Index"] == PlayerSingleton.currentNodeIndex:
				var node = dicts["Scene"]
				if node.get_node("NodeSprite").texture == preload("res://Sprites/skull.png"):
					node.get_node("AnimationPlayer").play("skull_finished")
				await get_tree().create_timer(1).timeout
				
				for path in node.paths:
					SoundManager.map_node_click.play()
					path.visible = true
					var pos = path.points[1]
					path.points[1] = path.points[0]
					
					# Create a tween and bind arguments to the method
					var tween = get_tree().create_tween()
					tween.tween_method(
						Callable(self, "_set_path_point").bind(path, 1), # Bind path and index
						path.points[1],
						pos,
						1
					)
					
					await get_tree().create_timer(0.3).timeout

# Function to dynamically update the array
func _set_path_point(new_value, path, index):
	path.points[index] = new_value

func _on_TimerTimeout() -> void:
	# Check if we've reached the max node count, stop if so
	if nodeCount >= maxNodes:
		nodeCreationTimer.stop()
		connectRemainingNodes()
		changeNodeTypes()
		createProps()
		%Camera.position.y = rootNode.position.y + 350
		for node in nodes:
			if PlayerSingleton.activeNodes.has(node["Index"]):
				node["Scene"].active = true
			if PlayerSingleton.finishedNodes.has(node["Index"]):
				node["Scene"].finished = true
		return
	
	# Continue creating nodes if we haven't reached max nodes
	for parent in newParentNodes:
		if parent == rootNode and parent.children.size() >= 3:
			continue
		
		var rng = randi_range(1, 2)
		
		for count in range(rng):
			# Only create nodes if we haven't hit maxNodes
			if nodeCount >= maxNodes:
				break
			
			var child = nodeScene.instantiate()
			child.paths = []
			child.children = []
			child.connectionCount = 0  # Initialize connection count for the new node
			child.maxConnections = 2  # Non-root nodes can connect to up to 2 nodes
			child.scene = encounterScene
			child.visible = false
			var newPos = generateValidPosition(parent.position)
			
			# If a valid position wasn't found (Vector2.ZERO), remove the node
			if newPos == Vector2.ZERO:
				#print("Failed to place node after multiple attempts, removing node.")
				child.queue_free()  # Remove the node from the scene
				continue  # Skip this node creation

			child.position = $"../TileMapLayer".map_to_local(newPos)
			add_child(child)
			
			var line = preload("res://Scenes/StageMaps/line_2d.tscn").instantiate()
			line.visible = false
			line.add_point(parent.position)
			parent.paths.append(line)
			parent.children.append(child)
			line.add_point(child.position)
			add_child(line)
			
			nodeCount += 1
			child.mapIndex = nodeCount
			nodes.append({"Scene": child, "Index": child.mapIndex, "pos": child.position})
			queuedChildren.append(child)
	
	# Update newParentNodes for next iteration
	if queuedChildren.size() > 0:
		for node in queuedChildren:
			newParentNodes.append(node)
		queuedChildren.clear()

# Modify generateValidPosition to return Vector2.ZERO on failure
func generateValidPosition(parentPosition: Vector2) -> Vector2:
	var newX = parentPosition.x + randi_range(-500, 500)
	
	while newX < -500 or newX > 500:
		newX = parentPosition.x + randi_range(-500, 500)
		
	var newPos = Vector2(newX, parentPosition.y + randi_range(200, 500))
	
	var tooClose = true
	var attempts = 0
	while tooClose and attempts < 20:
		tooClose = false
		for node in nodes:
			var existingNode = node["Scene"]
			if newPos.distance_to(existingNode.position) < minDistance:
				tooClose = true
				newPos = Vector2(newX, parentPosition.y + randi_range(200, 500))
				break
		attempts += 1
	
	if attempts == 20:
		# If we exceed the attempt limit, return Vector2.ZERO to indicate failure
		return Vector2.ZERO
	
	newPos = $"../TileMapLayer".local_to_map(newPos)
	return newPos

func connectRemainingNodes():
	for node in nodes:
		var sceneNode = node["Scene"]
		if not sceneNode.connected:
			var potentialConnections = []
			for otherNode in nodes:
				var otherSceneNode = otherNode["Scene"]
				# Skip nodes that are the same, and check if both nodes have space for connections
				if otherSceneNode != sceneNode and otherSceneNode.position.y > sceneNode.position.y:
					# Only consider connections within the max distance threshold
					var dist = sceneNode.position.distance_to(otherSceneNode.position)
					if dist <= maxConnectionDistance:
						if otherSceneNode.connectionCount < otherSceneNode.maxConnections and sceneNode.connectionCount < sceneNode.maxConnections:
							potentialConnections.append({"Node": otherSceneNode, "Distance": dist, "Index": otherNode["Index"], "Pos": otherSceneNode.position})
			
			# Sort potential connections based on distance (nearest first)
			potentialConnections.sort_custom(_compareByDistance)
			
			# will try to connect with the closest available node(s)
			var closestNodes = potentialConnections.slice(0, 3)  # Get up to 3 closest nodes

			if closestNodes.size() > 0:
				# Randomly select one of the closest nodes (we only need to connect one)
				var selectedNode = closestNodes[randi_range(0, closestNodes.size() - 1)]["Node"]

				# Increment the connection count for both the selected node and the current node
				sceneNode.connectionCount += 1
				selectedNode.connectionCount += 1

				# Mark both nodes as connected
				sceneNode.connected = true
				selectedNode.connected = true

# Comparator function to sort by distance (ascending order)
func _compareByDistance(a, b):
	if a["Distance"] < b["Distance"]:
		return -1
	elif a["Distance"] > b["Distance"]:
		return 1
	else:
		return 0

func changeNodeTypes():
	var changedNodes : Array
	
	#changes node to shop
	for count in maxShopNodes:
		var randomNode = nodes.pick_random()
		#Checks if the random node is the rootnode, pick another if so
		while randomNode["Index"] == 1:
			randomNode = nodes.pick_random()
		#Checks if the random node is a node already changed, pick another if so
		while changedNodes.has(randomNode["Scene"]):
			randomNode = nodes.pick_random()
		randomNode["Scene"].get_node("NodeSprite").texture = load("res://Sprites/shop.png")
		randomNode["Scene"].get_node("NodeSprite").scale = Vector2(2,2)
		randomNode["Scene"].scene = load("res://Scenes/StageMaps/CardShop.tscn")
		changedNodes.append(randomNode["Scene"])
	
	##changes nodes to random events
	#for count in maxRandomNodes:
		#var randomNode = nodes.pick_random()
		##Checks if the random node is the rootnode, pick another if so
		#while randomNode["Scene"] == rootNode:
			#randomNode = nodes.pick_random()
		##Checks if the random node is a node already changed, pick another if so
		#while changedNodes.has(randomNode["Scene"]):
			#randomNode = nodes.pick_random()
		#randomNode["Scene"].get_node("NodeSprite").texture = load("res://Sprites/question.png")
		#changedNodes.append(randomNode["Scene"])
	
	nodes.sort_custom(func(a, b): return a.pos.y > b.pos.y)
	bossNode = nodes[0]["Scene"]
	bossNode.get_node("NodeSprite").texture = load("res://Sprites/boss_skull.png")
	bossNode.scene = bossStage
	
	%Camera.minScroll = rootNode.position.y + 300
	%Camera.maxScroll = bossNode.position.y - 300

func createProps():
	for count in 45:
		var props : Array
		
		var newProp = Sprite2D.new()
		newProp.texture = mapProps.pick_random()
		newProp.texture_filter = 1
		newProp.scale = Vector2(4, 4)
		
		var xpos = randi_range(-11, 11)
		var ypos = randi_range(0, $"../TileMapLayer".local_to_map(bossNode.position).y)
		var pos = Vector2(xpos, ypos)
		
		for prop in props:
			var existingProp = prop
			if newProp.distance_to(prop) < minDistance:
				continue
		
		for i in nodes.size():
			if nodes[i]["pos"] == pos:
				continue
		
		props.append(newProp)
		
		newProp.position = $"../TileMapLayer".map_to_local(pos)
		add_child(newProp)

func _process(delta: float) -> void:
	if Input.is_action_just_pressed("testKey"):
		SceneTransition.changeScene(get_tree().current_scene.get_path())

Im using seeds so the player can keep the same map when switching between scenes. When I first made the scripts it worked perfectly fine without fail, but when I started to build upon it more it started to not keep the same map in-between scene switching and I can’t figure out why. Any thoughts or solution?

You should use a dedicated RandomNumberGenerator in case anything else is using the global one

var rng := RandomNumberGenerator.new()

func _ready() -> void:
	rng.seed = PlayerSingleton.currentSeed
1 Like

Thanks! that was a speedy reply

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