I have a scene with @export vars that has been initialized in multiple levels.
The first level works perfectly fine(which was also instantiated by my scene_manager). When i instantiate the next level, the @export variable defaults to the scene’s (not the instance’s) value.
(tried to include images but the forum stopped me)
this works for one scene but not the other, and both are brought in the same way.
extends Area2D
@export var the_entrance: Node2D
@export var transition_name: String
@export var scene_to_load: PackedScene:
set(value):
scene_to_load = value
var should_load: bool = false
func _process(delta):
if should_load:
should_load = false
SceneManager.goto_scene(scene_to_load.resource_path)
Is this a bug or am i using the system wrong? I’m honestly rather new to Godot so maybe i am expecting these to work like Unity’s [SerializeField].
From the code you provided, are the export variables that are taking the default values in the scene_to_load?
If so, then I imagine when goto_scene is called it instantiates the scene_to_load, and since you’re not then explicitly setting the export vars, its using the scene’s default values. You should be able to then set the export vars by setting them in code, e.g. scene_to_load.export_var = new_value.
Having not used PackedScene’s often, I might be wrong, but my approach is along these lines and it works fine:
Sub-scenes (e.g. enemy, pickups, other entities) have export vars
Level scenes contain numerous sub-scenes with values set for exports
Scene manager selects “LevelScene” to load which it then instantiates. Level scenes don’t contain there own exports, so nothing needs setting when they’re loaded.
That sounds pretty reasonable to me. You area is essentially a doorway to another scene, and contains export vars that configure which scene will be loaded when the player exits the doorway.
And those export vars aren’t working? Can you post some code to look at?
I apologize in advance for some sorry-looking code and appreciate the help.
rest of the Area2D’s script
extends Area2D
@export var the_entrance: Node2D
@export var transition_name: String
@export var scene_to_load: PackedScene:
set(value):
scene_to_load = valuew
var should_load: bool = false
func _ready():
pass
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
if should_load:
should_load = false
SceneManager.goto_scene(scene_to_load.resource_path)
func _on_body_entered(body):
if body.get_groups()[0] == "Players":
should_load = true
body.areaTransitionName = transition_name
The issue im having is after we already loaded the ‘temp’ scene, and would be calling goto_scene again to unload the temp scene to go back to the Town scene we came from.
SceneManager
extends Node
var current_scene = null
var prev_scene = null
@onready var root = get_tree().root
func _ready():
#var root = get_tree().root
current_scene = root.get_child(root.get_child_count() - 1)
print(current_scene.name)
func goto_scene(path):
if current_scene == null:
current_scene = root.get_child(root.get_child_count() - 1)
if prev_scene != null:
call_deferred("exit_temp_scene")
elif current_scene.name == "Town":
call_deferred("_deferred_goto_temp_scene", path)
else:
call_deferred("_deferred_goto_scene", path)
func _deferred_goto_scene(path):
if current_scene.has_method("save_character"):
current_scene.save_character()
current_scene.free()
var s = ResourceLoader.load(path)
current_scene = s.instantiate()
if current_scene.has_method("load_character"):
current_scene.load_character()
get_tree().root.add_child(current_scene)
get_tree().current_scene = current_scene
func _deferred_goto_temp_scene(path):
prev_scene = current_scene
var s = ResourceLoader.load(path)
current_scene = s.instantiate()
get_tree().root.add_child(current_scene)
prev_scene.get_node("PlayerPackage").reparent(current_scene)
get_tree().root.remove_child(prev_scene)
get_tree().current_scene = current_scene
func exit_temp_scene():
get_tree().root.add_child(prev_scene)
current_scene.get_node("PlayerPackage").reparent(prev_scene)
current_scene.free()
current_scene = prev_scene
prev_scene = null
get_tree().current_scene = current_scene
The first level after the main screen i am loading uses the first _deferred_goto_scene(), whereas the method being used when i am encountering this is the _deferred_goto_temp_scene(), as i was originally going to keep the prior scene loaded to continue doing processing.
Considering this issue is noticeable in debug by the 'current_scene = s.instantiate()" line, could this be due to the fact that I’m not .free()-ing the prior scene?
That is the only difference at this point of the load process… but i eventually want NPCs with their own patterns to be processed while the player is in other levels, and I’m unsure of how to accomplish this if i need to .free() each scene as i leave it.
That issue may be it’s own and requiring a separate thread, however.
So, I was researching another test project i had, and when trying to reload this one, i got errors about broken dependencies…
Apparently, having 2 scenes that have each other as values causes an issue as it can resolve one without loading the other. basically preventing one from working…
So it’s back to the drawing board…
EDIT: If i just used NodePaths to the scenes instead of trying to attach the scene itself, it works fine…
I’ve got a bit lost in where the problem is now, as having 2 scenes reference each other is a slightly different issue than the export vars issue. However, avoiding circular dependencies is generally a good idea, especially in your case, where it clearly breaks the design you wanted to follow.
I guess the key question is why do you want scenes to reference each other directly? Is it for sharing (or moving) state between them, or is there another reason?
I’m not sure if this is at all helpful, but I personally use ResourcePaths (e.g. res://) for navigating between scenes, and if there’s any state that needs to cross between them, I do it through an autoload.
Essentially my design looks roughly like this:
GameManager (autoload) - stores state for the game (e.g. player lives, score, and other data that needs to survive across scenes). It also contains a dictionary with scene names and res paths, and some functions to navigate between the scenes, for example, load_scene(scene_name : String).
EventBus (autoload) - defines a load of game-related signals
ocean_scene.tscn - contains my ocean scene.
forest_scene.tscn - contains my forest scene.
interstitial_scene.tscn - contains a transient scene for showing when transitioning.
A scene can then signal it wants a transition by emitting the navigate_scene(scene_name : String) signal. The GameManager listens for that signal, and then handles loading the interstitial_scene, loading the next scene, and transferring between them. Any state that needs to cross between the scenes is held by the game manager.
The scenes are therefore loosely coupled and know v.little about each other, except for each others names.
This was something I’ve started to realize as well as i looked further into the issue.
I am very likely going to do exactly that and redesign my SceneManager to be the master of the scene references.
Since Autoloads are generally siblings of the node that get added to root, does that bring in complications with listening to the signals being created by the scenes or their children?
Or does having it as an Autoload simplify that?
May i also ask if you just switch between scenes within the root or do you keep them contained in another scene(i.e. Main)?
Thank you for this as I’ve had a hard time gleaming this information from the Docs.
My scenes have a dependency on the event bus, in that they fire the signals it defines, and my GameManager has a dependency on the event bus, in that it listens for those signals. The event bus being a sibling of the root-added nodes hasn’t caused me any problems, and I believe its fine.
Ah ok, I should definitely refer to more of the GDQuest content going forward then.
and i assume your GameManager maintains your player states as well as handles their loading into the level?
I was planning on having NPCs that continue to follow their patterns even when the player is in another scene, but there may be a better method to simulate that than to actually have the characters and scene being processed.
In my case there’s actually v.little player state, so little, in fact, that the in-scene player doesn’t need to know any of the state. If my in-level player needed the state then I guess I’d have done what you suggested, and had the GameManager load it into the scene player.
The NPCs continuing to run is also an interesting challenge that I haven’t had to deal with. Clearly you’ll need something that maintains some of their state, and maybe runs or simulates a crude version of their in-scene behaviour.
If I had to retrofit it to my game I think I’d add some kind of NPCManager class, and possibly hang that off my GameManager, but there might be better approaches.