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?