Having trouble with spawning a randomly generated dungeon (instantiating in particular)

Godot Version

4.3

Question

So i’ve been working on a function that’s supposed to generate a dungeon (kind of like enter the gungeon), i’ve managed to make it work to an extent but i’m having problems now because i can only spawn each room once before i get an error message, i thought i could get around this by instantiating the room that was picked at random and then adding it as a child of the previous room but that doesn’t work since it returns the following error :

Invalid call. Nonexistent function ‘instantiate’ in base ‘Node2D (Room)’.

from what i understood this is because you can’t use instantiate on something else than a PackedScene.
i could get around this by declaring all rooms as PackedScene and getting them into groups depending on which side they can spawn from but that sounds like a headache and i’d really like to keep the current code i made or at least the general idea.

what i’m asking can be summed up to either, How do i spawn more than one time a room in this context or how can i use instantiate (or something similar) to spawn as many times and wherever i want a room.

here’s a look at the code (do note that i’ve only done the code for rooms that spawn on the top side of other rooms)


a few things that can help for context :

as of right now when a room is spawned it is spawned from a Mark (either a Top, Bottom, left Or Right Mark)
to know which room can spawn from which mark i made 4 types of group (Top, Bottom etc…)
I’m pretty new when it comes to coding, that’s why the code might look messy/unoptimized ( i do plan on watching a tutorial after managing to make my own code work to see what i can or should do differently).

Also if that changes anything i instantiated all rooms to level (in the editor, not via code), which i then use remove child to clear (using queue_free returns some errors) if i don’t do that i get some errors.

if anyone needs some more info on the code feel free to ask

First off, it looks like you are using your Top_Rooms variable as an array, but declaring it as a Variant. This means you won’t get certain code completion in the editor, and if you’re loading in something other than a PackedScene, you won’t know it because no error will be thrown. Secondly, it’s making your code more complex than it needs to be. Third, capitalizing your variable name makes it look like an Class type, which makes your code more confusing to read. So use this:

@onready var top_rooms: Array[PackedScene] = get_tree().get_nodes_in_group("Top")

Next, replace lines 45 and 46 with:

var room_instance = top_rooms.pick_random()

Try this and see if the errors change. What I suspect is happening is that your Top_Rooms variables, as a variant, is getting assigned a new value of Node2D. Changing the code as I have suggested will not allow that to happen, and show you where you are doing this so you can fix it.

1 Like

hey, thanks for the answer. i tried to change the line as you said but it returns this error, do note that each room has it’s own scene as a node 2d of the class Room, i’ve tried to make them extends to Packed Scene but it didn’t work

okay so i managed to find a way to do what i had in mind :

what i needed was to be able to spawn multiple time the same room, the issue i had was that i couldn’t do that with .instantiate() because it only works on PackedScene.

i also thought that i couldn’t use PackedScenes in a convenient way because i thought i would have to declare PackedScenes with load and then assign them manually to their groups to tell the game where they can and can’t spawn since using is_in_group() doesn’t work on a PackedScene.

however i found on this thread : Accessing some (but not all) information from a PackedScene - #2 by a52

that PackedScenes have a method called get_state()

get_state() allows you to see info about a PackedScene that you wouldn’t be able to see otherwise.

in my case i was able to see the groups of the PackedScenes under the form of a PackedStringArray and assign the scenes depending on the values in the PackedStringArray :

func sort():
	for scene in sorting_list:
		var scene_groups = scene.get_state().get_node_groups(0)
		if scene_groups.has("Bottom") == true:
			bottom_rooms.append(scene)
		if scene_groups.has("Top") == true:
			top_rooms.append(scene)
		if scene_groups.has("Left") == true :
			left_rooms.append(scene)
		if scene_groups.has("Right") == true:
			right_rooms.append(scene)
		if scene_groups.has("Starting_Room") == true:
			starting_rooms.append(scene)

so here i made a for loop that runs for as many scenes as there is in sorting_list (i had to put all my rooms in my sorting_list manually because i don’t have any idea on how to do it via code)

then i create the var scene_groups which gets from the scene a PackedStringArray that contains the groups that the PackedScene has.

at first i thought that PackedStringArrays worked just like arrays but not really so you can’t use the methods you would otherwise

thanks to the godot documentation that you get when pressing ctrl click i found the methods .find() and .has() for PackedStringArrays

I tried using .find() first but it didn’t work quite as i hoped it would.
however, .has() was just what i needed, it checks the packedStringArray and sees for each value if it corresponds to what you put in the () of the method.

anyways here’s the full code (unfinished) code for anyone intrested :


extends Node2D
var top_rooms  = []
var bottom_rooms = []
var left_rooms = []
var right_rooms = []
var starting_rooms = []
@onready var Vcorridor : PackedScene = load("res://Rooms/Stage 1/Vcorridor.tscn")
@onready var Hcorridor : PackedScene = load("res://Rooms/Stage 1/Hcorridor.tscn")
@onready var Start_Room1 : PackedScene = load("res://Rooms/Stage 1/start_room_1.tscn")
@onready var Start_Room2 : PackedScene = load("res://Rooms/Stage 1/start_room2.tscn")
@onready var  BDE1 : PackedScene = load("res://Rooms/Stage 1/bde_1.tscn")
@onready var start_room_spawned : bool = false
@onready var rng = RandomNumberGenerator.new()
@onready var starting_room 
@onready var current_child_number = 0
@onready var budget = 5
@onready var sorting_list = [Vcorridor,Hcorridor,Start_Room1,Start_Room2,BDE1]


func _ready():
	Clear()
	sort()
	Spawn_Rooms(null)

func sort():
	for scene in sorting_list:
		var scene_groups = scene.get_state().get_node_groups(0)
		if scene_groups.has("Bottom") == true:
			bottom_rooms.append(scene)
		if scene_groups.has("Top") == true:
			top_rooms.append(scene)
		if scene_groups.has("Left") == true :
			left_rooms.append(scene)
		if scene_groups.has("Right") == true:
			right_rooms.append(scene)
		if scene_groups.has("Starting_Room") == true:
			starting_rooms.append(scene)




func Clear():
	remove_child($Start_room2)
	remove_child($Start_Room1)
	remove_child($BDE1)
	remove_child($Hcorridor)
	remove_child($Vcorridor)



func Spawn_Rooms(Current_Room):
	if start_room_spawned == false:                                           #Looks if a starting room was created
		var random_room = rng.randi_range(0,starting_rooms.size()-1)
		var room_instance = (starting_rooms[random_room]).instantiate()
		add_child(room_instance) # picks a random room in the starting rooms array
		start_room_spawned = true
		Spawn_Rooms(room_instance)
	else:
		while budget > 0 :
			if is_instance_valid(Current_Room.get_child(1)) == true : #gets the first mark of the current room
				if Current_Room.get_child(1).is_in_group("Top_Mark") :
					var room_instance = top_rooms.pick_random().instantiate()
					Current_Room.get_child(1).add_child(room_instance) #spawns the room from the instance
					var child = Current_Room.get_child(1).get_child(0) #gets the room that was just spawned
					print(child)
					budget -= child.Budget_Cost
					while child.get_child(current_child_number).is_in_group("Bottom_Mark") == false :
						current_child_number+=1
					var mark_pos = child.get_child(current_child_number).position
					mark_pos = mark_pos*-1
					child.position = mark_pos
					Spawn_Rooms(child)
					
				elif Current_Room.get_child(1).is_in_group("Left_Mark") : 
					pass
				elif Current_Room.get_child(1).is_in_group("Right_Mark") :
					pass

right now all it does is spawn corridors on the top side of the starting room until the budget runs out but i solved my problem of not being able to create multiple time the same room (was mostly a lack of knowledge on PackedScenes to be honest)

Yeah, I expected it to throw that error or something like it. It’s telling you that what you’re putting in the array is not what you think you’re putting in there. That was the point of doing that.

Glad you found your solution!

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