Terrible lag while main game node is instantiated under a Global level loading node

Godot Version

v4.6.1

Question

I’m trying to get a main menu working for my game, and after some trial and error, I have landed on using a “Global Node” as the highest parent node, and loading the “main level” (title screen/game) under that. Issue is that after implementing this system, lag has jumped dramatically where before it was running fine. The lag appears even on the main menu with this system (checked with print(Engine.get_fps)). Also side note, after loading into my title screen from the game scene, all scripts either disappear or stop calling process(), ready(), etc. Im not sure which since none of the scripts work in that state so I cant check. But buttons still react to being hovered over so Im confused there.

Make sure to paste code instead of screen shots.

Your _process function is altering the window state constantly, you should only change fullscreen when needed. You could move the if Fullscreen into a separate function that you only call once Fullscreen is changed, and you can do that furthermore from a “setter”

var Fullscreen: bool = false : set = _my_fullscreen_setter

func _my_fullscreen_setter(new_value: bool) -> void:
    if new_value != Fullscreen:
        DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN if new_value else DisplayServer.WINDOW_MODE_WINDOWED)
        Fullscreen = new_value

This will automatically call the function to change window mode if Fullscreen is modified.

Not sure that your “global node” is the best way to implement a global, Godot does support globals on it’s own through “autoloads”

You also do not gain anything over get_tree().change_scene_to_file("res://main_menu.tscn") by using the global Swap_Scene.

Your UI components scripts may be better suited attached to the ui components themselves, or use the pressed signal to trigger a function on the Node3D, again avoiding _physics_process. Despite the template you should actually avoid using _process and _physics_process as much as possible, chances are there is a signal you can connect to for much better performance. Only constantly and dynamically moving things should benefit from _process.

I implemented a fix to this but the lag is still there

func _process(delta):
if (FullscreenChecked == Fullscreen):
if Fullscreen:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
else:
FullscreenChecked = !FullscreenChecked

I tried using a global script at first (hence trial-and-error) but it caused a bunch of issues with stuff I had coded that I didnt see any way to fix. Thats why I swapped to this system.

The reason I have a bunch of ui stuff attached to node3ds is because they are player-specific and its how I am storing all my other player “component” nodes. I am also avoiding manually connecting signals as Im trying to keep everything “modular” so I can reuse components elsewhere to make other stuff. Again there was no lag (save a crashing problem I solved before) before I made the main-menu scene switching setup.

This will set the full screen every other frame, still very taxing. If you implement this sample as-is you can remove the full screen from _process

var Fullscreen: bool = false : set = _my_fullscreen_setter

func _my_fullscreen_setter(new_value: bool) -> void:
	if new_value != Fullscreen:
        Fullscreen = new_value
		if Fullscreen:
			DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
		else:
	 		DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)

Using and connecting signals is very modular, even through the editor. Without the connection the script still runs fine minus one feature, and you could connect any applicable signal to the predefined function. Currently your get_parent().get_parent().get_parent() is the antithesis of modular, requiring a rigid scene tree structure or the entire script fails.

Maybe you should make a new help topic about that too, I’m sure there is a more Godot-friendly solution.

I mistyped that, I can fix that. Though Ive done some testing and that is NOT the source of the lag. The issue is the “Game” or “Main Menu” scene being loaded by something during runtime rather than being loaded with Main Scene.

In the setup of the screenshot provided, setting the Main Scene to this scene (Still named “game.tscn”) has no lag whatsoever and runs perfectly fine. When I instead set the Main Scene to “global_node.tscn”, the game lags severely. I tried changing “Global Node” from Node to Node3D and that still solves nothing

I might be dumb I fixed the thing and it fixed it

image

I have all these things contained neatly in designated “container” node3ds, if they are loaded correctly they can always access the desired node that way.

Also the issue with the global node is that when using get_tree() it has the same “level/parenthood” as the Main Scene, meaning it screws up a bunch of stuff in weird ways, for example it broke my code for stopping sounds (I have a sliding sound, and it does a check if the player is still sliding, and if it is, then it queue_free()’s itself). I dont see the point making a new topic considering trying to get my game loading from the title screen without it having issues is my current issue.

Ok I dont know what I was thinking but I completely failed to consider that not loading the Global Node would also not load the fullscreen setting mess, and I forgot to fix that bit of code so yeah turns out that was the fix I think, Ive commented out the code for now and Ill fix it in a sec

I would like to avoid implementing this bit of code as I dont understand most of it:

“…set = my_fullscreen_setter”
“…(new_value: bool) -> void:"

Thank you for providing it though :+1:

That’s a great policy

This assigns a setter to the variable, a setter is a special function that runs every time the variable is modified. In a bool’s case it will only ever change to true or false, so the setter function my_fullscreen_setter has to take a bool argument. It is very peculiar and has weird caveats so I don’t like to use it much, it basically turns your variable into a function (or two if you use a getter), and I’d rather be up front and use the function itself.

if you’d like you can omit the setter, potentially omit the variable too, and change fullscreen by calling my_fullscreen_setter(false)

This is static typing, it says the function takes new_value must be a bool (true/false) type, and returns nothing (void). Static typing helps you catch errors faster and more accurately, it can also speed up your game in some situations.

I get the static typing with :, Idk what the arrow does though. Also the way the setter works is really confusing but I got a way working and it isnt called every frame anymore and lag is gone, also fixed the issue with the buttons, turns out since I returned to the main menu while the game was paused, the main menu was also unresponsive, so I just changed processing stuff on those :+1:


func _process(delta):
	print(str(Fullscreen) + " and " + str(FullscreenChecked))
	if (FullscreenChecked == Fullscreen):
		if Fullscreen:
			DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
		else:
			DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
		FullscreenChecked = !FullscreenChecked

The arrow is the return type.

# takes two integers, returns another integer
func add(a: int, b: int) -> int:
    return a + b

I agree setters are confusing but I still believe you would be better off without this code in _process, the logic of it is still strange and you are checking two variables every frame. You should be thinking more event-driven rather than check-every-frame-driven.

So the arrow is like static typing for “return”?

Ill keep this in mind and try to clean up my code in my next project, for now I have so many scripts it would be impossible to rewrite it all before the jam deadline.