Options Menu Error "changed parent"

Godot Version

4.5.1

Question

I changed the parent of the BoxMenu now my code aint working, when I click Options in the GameMenu it shows me the error Invalid access to property or key ‘item_selected’ on a base object of type ‘null instance’. "see screenshot for more details"

It worked before (not perfect), I tried the name it correct in the :

UI refs

@onready var resolution_dropdown: OptionButton = $OptionsMenu/OptionMenu/OptionsBox/Resolution/ResolutionDropdown

extends Control

# -------------------------
# VARIABELEN
# -------------------------
var pending_resolution: Vector2i = Vector2i.ZERO
var old_resolution: Vector2i = Vector2i.ZERO
var countdown: int = 15
var countdown_timer: Timer
var fullscreen_pending := false

# UI refs
@onready var resolution_dropdown: OptionButton = $OptionsMenu/OptionMenu/OptionsBox/Resolution/ResolutionDropdown
@onready var fullscreen_check: BaseButton = $OptionsMenu/OptionMenu/OptionsBox/Fullscreen/FullscreenCheck
@onready var subtitles_check: BaseButton = $OptionsMenu/OptionMenu/OptionsBox/Subtitles/SubtitlesCheck
@onready var language_dropdown: OptionButton = $OptionsMenu/OptionMenu/OptionsBox/Language/LanguageDropdown
@onready var music_slider: HSlider = $OptionsMenu/OptionMenu/OptionsBox/"Music Volume"/MusicSlider
@onready var speech_slider: HSlider = $OptionsMenu/OptionMenu/OptionsBox/"Speech Volume"/SpeechSlider
@onready var sfx_slider: HSlider = $OptionsMenu/OptionMenu/OptionsBox/"SFX Volume"/SFXSlider

@onready var resolution_popup: Popup = $ResolutionPopup
@onready var countdown_label: Label = $ResolutionPopup/CountdownLabel
@onready var apply_button: TextureButton = $ResolutionPopup/ApplyButton
@onready var cancel_button: TextureButton = $ResolutionPopup/CancelButton

@onready var save_button = $SaveButton
@onready var exit_button = $ExitButton


# -------------------------
# READY
# -------------------------
func _ready() -> void:
	# Timer
	countdown_timer = Timer.new()
	countdown_timer.wait_time = 1.0
	countdown_timer.one_shot = false
	add_child(countdown_timer)
	countdown_timer.timeout.connect(_on_countdown_tick)

	# Signals via code (dan hoef je in de editor minder te klikken)
	resolution_dropdown.item_selected.connect(_on_resolution_selected)
	apply_button.pressed.connect(_on_apply_pressed)
	cancel_button.pressed.connect(_on_cancel_pressed)
	save_button.pressed.connect(_on_save_button_pressed)
	exit_button.pressed.connect(_on_exit_button_pressed)
	fullscreen_check.toggled.connect(_on_fullscreen_check_toggled)
	subtitles_check.toggled.connect(_on_subtitles_check_toggled)
	language_dropdown.item_selected.connect(_on_language_dropdown_item_selected)
	music_slider.value_changed.connect(_on_music_slider_value_changed)
	speech_slider.value_changed.connect(_on_speech_slider_value_changed)
	sfx_slider.value_changed.connect(_on_sfx_slider_value_changed)

	load_settings()



# -------------------------
# LOAD / SAVE
# -------------------------
func load_settings() -> void:
	var config := ConfigFile.new()
	if config.load("user://settings.cfg") != OK:
		return

	var res_id: int = config.get_value("video", "resolution", 0)
	resolution_dropdown.select(res_id)
	_apply_resolution_silent(res_id)

	var fullscreen_cfg: bool = config.get_value("video", "fullscreen", false)
	fullscreen_pending = fullscreen_cfg
	fullscreen_check.button_pressed = fullscreen_cfg
 

	music_slider.value = config.get_value("audio", "music", 50)
	speech_slider.value = config.get_value("audio", "speech", 50)
	sfx_slider.value = config.get_value("audio", "sfx", 50)

	subtitles_check.button_pressed = config.get_value("gameplay", "subtitles", false)

	var lang_id: int = config.get_value("gameplay", "language", 0)
	language_dropdown.select(lang_id)


func save_settings() -> void:
	var config := ConfigFile.new()

	config.set_value("video", "resolution", resolution_dropdown.get_selected_id())
	config.set_value("video", "fullscreen", fullscreen_check.button_pressed)
	config.set_value("audio", "music", music_slider.value)
	config.set_value("audio", "speech", speech_slider.value)
	config.set_value("audio", "sfx", sfx_slider.value)
	config.set_value("gameplay", "subtitles", subtitles_check.button_pressed)
	config.set_value("gameplay", "language", language_dropdown.get_selected_id())

	config.save("user://settings.cfg")


# -------------------------
# RESOLUTIE
# -------------------------
func _apply_resolution_silent(index: int) -> void:
	var res_text := resolution_dropdown.get_item_text(index)
	var parts := res_text.split("×")
	if parts.size() != 2:
		return
	var width := int(parts[0])
	var height := int(parts[1])
	DisplayServer.window_set_size(Vector2i(width, height))



func _on_resolution_selected(index: int) -> void:
	var res_text := resolution_dropdown.get_item_text(index)
	var parts := res_text.split("×")
	if parts.size() != 2:
		return

	pending_resolution = Vector2i(int(parts[0]), int(parts[1]))
	old_resolution = DisplayServer.window_get_size()

	# Forceer windowed (2-frame fix)
	DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
	await get_tree().process_frame
	

	DisplayServer.window_set_size(pending_resolution)
	
	print("PREVIEW REQUEST:", pending_resolution)
	await get_tree().process_frame
	print("WINDOW AFTER REQUEST:", DisplayServer.window_get_size())


	# Popup openen
	countdown = 15
	countdown_label.text = str(countdown)
	resolution_popup.popup_centered()

	countdown_timer.start()


# -------------------------
# COUNTDOWN / REVERT
# -------------------------
func _on_countdown_tick() -> void:
	countdown -= 1
	if countdown_label:
		countdown_label.text = str(countdown)

	if countdown <= 0:
		_revert_resolution()


func _revert_resolution() -> void:
	countdown_timer.stop()
	resolution_popup.hide()
	DisplayServer.window_set_size(old_resolution)

	# Dropdown terugzetten naar oude resolutie
	for i in range(resolution_dropdown.item_count):
		if resolution_dropdown.get_item_text(i) == str(old_resolution.x) + "×" + str(old_resolution.y):
			resolution_dropdown.select(i)
			break


# -------------------------
# APPLY / CANCEL
# -------------------------
func _on_apply_pressed():
	countdown_timer.stop()
	resolution_popup.hide()

	# resolutie opslaan
	save_settings()
	old_resolution = pending_resolution

	# fullscreen NA apply toepassen
	if fullscreen_pending:
		DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
	else:
		DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)


func _on_cancel_pressed() -> void:
	_revert_resolution()


# -------------------------
# OVERIGE SIGNALS
# -------------------------
func _on_fullscreen_check_toggled(toggled_on: bool) -> void:
	fullscreen_pending = toggled_on


func _on_subtitles_check_toggled(toggled_on: bool) -> void:
	print("Subtitles:", toggled_on)


func _on_language_dropdown_item_selected(index: int) -> void:
	print("Language set to:", language_dropdown.get_item_text(index))


func _on_music_slider_value_changed(value: float) -> void:
	AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Music"), linear_to_db(value / 100.0))


func _on_speech_slider_value_changed(value: float) -> void:
	AudioServer.set_bus_volume_db(AudioServer.get_bus_index("Speech"), linear_to_db(value / 100.0))


func _on_sfx_slider_value_changed(value: float) -> void:
	AudioServer.set_bus_volume_db(AudioServer.get_bus_index("SFX"), linear_to_db(value / 100.0))


func _on_exit_button_pressed() -> void:
	get_tree().change_scene_to_file("res://scenes/MainMenu.tscn")


func _on_save_button_pressed() -> void:
	save_settings()

Some one can help me out ? Im not a script writer can understand it a bit, using Copilot to help me but it getting frustated to work with it :slight_smile:

The path in your @onready var changed when you changed the parent. Fix those and the problem will go away.

Hi dragonforge-dev thank you for your answer.

Yes, I tought the same, that what I tried to do but cant seem to find the right one.

@onready var resolution_dropdown: OptionButton = $OptionsMenu/OptionMenu/OptionsBox/Resolution/ResolutionDropdown

@onready var resolution_dropdown: OptionButton = $OptionsMenu/OptionsBox/Resolution/ResolutionDropdown

@onready var resolution_dropdown: OptionButton = $OptionsMenu/Resolution/ResolutionDropdown

tried those 3 none did work, any suggestions ?

also tried

@onready var resolution_dropdown: OptionButton = $“../OptionMenuBCKgrnd/OptionsBox/Resolution/ResolutionDropdown”

as AI suggested I changed the name off the 2nd OptionMenu and add those .. infront

Had to change the root as Script was attached wrong for the code to work its fixed now thanks for the push in right direction

@onready var resolution_dropdown: OptionButton = $OptionsBox/Resolution/ResolutionDropdown
@onready var fullscreen_check: BaseButton = $OptionsBox/Fullscreen/FullscreenCheck
@onready var subtitles_check: BaseButton = $OptionsBox/Subtitles/SubtitlesCheck
@onready var language_dropdown: OptionButton = $OptionsBox/Language/LanguageDropdown
@onready var music_slider: HSlider = $OptionsBox/“Music Volume”/MusicSlider
@onready var speech_slider: HSlider = $OptionsBox/“Speech Volume”/SpeechSlider
@onready var sfx_slider: HSlider = $OptionsBox/“SFX Volume”/SFXSlider

@onready var resolution_popup: Popup = $ResolutionPopup
@onready var countdown_label: Label = $ResolutionPopup/CountdownLabel
@onready var apply_button: TextureButton = $ResolutionPopup/ApplyButton
@onready var cancel_button: TextureButton = $ResolutionPopup/CancelButton

@onready var save_button: TextureButton = $OptionsBox/SaveButton
@onready var exit_button: TextureButton = $OptionsBox/ExitButton

1 Like

I’m glad you got this fixed. Some advice:

Stop asking AI LLMs for help. They do not know how Godot works. Your code looked like LLM-generated code, so instead of me wasting my time reading it, I gave you a very general answer. I have no interest in fixing LLM code personally. Maybe someone else will, but you’d be better off learning how to solve this problem yourself by learning how everything works. Otherwise, the farther you go down the LLM rabbit hole, the harder it is going to be to fix things.

I recommend as a learning exercise, you take each piece of code and make a separate script for each option in your menu. It will solve your @onready variable problem because you won’t need them. It will also make sure you understand how everything works.

If you really want to just fix the @onready variable problem, do this:

  1. Delete the @onready variable.
  2. Right-click on the node you want to link.
  3. Select % Access as Unique Name from the drop-down.
  4. Click and drag the node to the script.
  5. Before dropping it, press and hold the Ctrl (Command on a Mac) key.
  6. While holding the Ctrl key, drop the node into your code. The @onready variable will appear.

Using a unique name means that the reference to the node will not change if the node structure changes again.

1 Like