Switch scene, keep object states

Godot Version

3.5.3

Question

Root scene loaded. As child, current open room scene. To switch rooms, queue free the current room scene and instanciate the new room. Easy, right?

Let’s say we have in a room a chest that can be interacted and opened by the player. If it’s opened once, it’ll stay this way forever as long as we don’t close the game, or switch rooms. Makes sense, indeed.

What you could do (and what I use) is have the chest have an ID export or something and append it to an array, and when the chest (and the rest of the room is loaded) check if it’s ID is already in the array, and if it is, just make it opened! That works, but… I really don’t like it.

IDs might overlap, so you always gotta remember and/or check if there’s an ID you don’t already use. It’s the definition of annoying. I did think of using RIDs of export resources that are local to scene, but yeah apparently RIDs are broken or something?

If anybody came up with a solution (of course someone came up with one), please tell me because I am going insaneeeeeeeeeee ove this! Thanks in advance!!!

need to know how do you generate the room, and know how the chest is spawned or not, or it is all randomly generated?
do you use a save data or every time game started it’s a new game?

also how the scene tree looks like when the game is running (screenshot the remote tab’s scene tree)

The only way is to keep the state of the room/level saved somewhere like you do right now and apply it when loading the room/level again.

RID is for low-level resources in servers, you can’t create one manually.

Oh. Ok then, I guess. I could use, like, randomly huge numbers for the IDs. Would make it less likely for 'em to be overlaupping. Thanks to you!

Have a singleton like StateMgr that stores state of rooms, if you don’t have another place already:

var states: Dictionary # dictionary of dictionaries

func get_state(id: String):
    return states.get(id)

func save_state(id: string, state: Dictionary) -> void:
    states[id] = state

func purge_state(ids_that_start_with: String):
    var to_erase = []
    for key in states:
       if key.left(len(ids_that_start_with)) == ids_that_start_with:
          to_erase.append(key)

    for key in to_erase:
        states.erase(key)

Give each room a string key/id, “room_id.” A unique ID for the chest would then can be room ID + position:

gp = chest.global_position
chest.id = room_id + str(gp.x) + str(gp.y)
chest.state = StateMgr.get_state(chest.id)

in chest.gd script:

func _ready():
    if not state:
       state = default_state()

    update_from_state()

func update_from_state():
   chest.opened = state["opened"]
   # etc...

func default_state() -> Dictionary:
   return {"opened" : false} # and w/e other properties

func open_chest():
   # show open sprite or w/e
   # and then update state
   state["opened"] = true

When player is about to leave the room:

for c in chests: # replace this with a way to iterate over chests
    StateMgr.save_state(c.id, c.state)

And ofc once you’re ready to purge state for the room (if ever):
StateMgr.purge_state(room_id)

or something like that :smiley:

PS: and you may have noticed that StateMgr can really store state for any object. Just keep the responsibility of reading/writing each type of state within the bounds of the object to whom the state applies and you’ll avoid spaghetti code and will have a generic state mgr/storage.