extends Node2D # The menu stacks whatever entries are actually available, rather than # trusting the positions authored in the scene. # # Two of the three entries only exist when the game is run from Godot, so a # shipped build shows one entry and an editor run shows three. There is no # Quit: a web build has nothing to quit TO, and on Android the system back # gesture already does it. Fixed # offsets would leave holes where the hidden entries used to be - the same # failure the HUD's button columns had, where a literal y meant "below the # thing above me" and stopped being true when that thing moved. Here the # stack is measured instead, and centred on whatever it ends up containing. const ROW_BUTTON_HEIGHT: float = 128.0 const ROW_LABEL_OFFSET: float = 134.0 const ROW_LABEL_HEIGHT: float = 56.0 const ROW_HEIGHT: float = ROW_LABEL_OFFSET + ROW_LABEL_HEIGHT const ROW_GAP: float = 20.0 const ROW_PITCH: float = ROW_HEIGHT + ROW_GAP # The stack is centred on this rather than on the screen, to leave the title # above it room to breathe. const STACK_CENTER_Y: float = 40.0 const STATUS_GAP: float = 20.0 const STATUS_HEIGHT: float = 70.0 @onready var play_button: IconButton = $UI/PlayButton @onready var play_label: Label = $UI/PlayLabel @onready var editor_button: IconButton = $UI/EditorButton @onready var editor_label: Label = $UI/EditorLabel @onready var dev_button: IconButton = $UI/DevModeButton @onready var dev_label: Label = $UI/DevModeLabel @onready var status_label: Label = $UI/StatusLabel func _ready() -> void: play_button.pressed.connect(_on_play_pressed) editor_button.pressed.connect(_on_editor_pressed) editor_button.enabled = true dev_button.pressed.connect(_on_dev_mode_pressed) dev_button.enabled = true # Dev mode and the map editor are development tools, not features of the # game: they are for building and play-testing it, and a player who # installed a build has no use for either. `editor` is the feature tag # Godot sets when running from the editor and clears in an export, which # is exactly the "started from Godot" distinction wanted here. var developing: bool = is_developer_build() editor_button.visible = developing editor_label.visible = developing dev_button.visible = developing dev_label.visible = developing _layout_stack() # Wrapped so tests can state the rule they are checking rather than # duplicating the feature-tag string. func is_developer_build() -> bool: return OS.has_feature("editor") func _layout_stack() -> void: var rows: Array = [] for row in [[play_button, play_label], [editor_button, editor_label], [dev_button, dev_label]]: if row[0].visible: rows.append(row) if rows.is_empty(): return # The last row carries no trailing gap, so the block is one pitch per row # minus that gap. var block_height: float = float(rows.size()) * ROW_PITCH - ROW_GAP var top: float = STACK_CENTER_Y - block_height * 0.5 for row in rows: var button: Control = row[0] var label: Label = row[1] button.offset_top = top button.offset_bottom = top + ROW_BUTTON_HEIGHT label.offset_top = top + ROW_LABEL_OFFSET label.offset_bottom = top + ROW_LABEL_OFFSET + ROW_LABEL_HEIGHT top += ROW_PITCH # Status sits under the whole stack, where a message about any entry can # be read without it shifting the entries themselves. var status_top: float = top - ROW_GAP + STATUS_GAP status_label.offset_top = status_top status_label.offset_bottom = status_top + STATUS_HEIGHT func _on_play_pressed() -> void: GameState.reset_new_game() _start("res://Main.tscn") # "Nothing happens when I press Play" covers three different faults, and they # are indistinguishable from the outside: the press never reaching this # handler, the scene change refusing, or the scene change reporting success # and not taking effect. Each writes a DIFFERENT line here, so one look at # the screen says which one it is. # # (nothing at all) - the press never got here # "Loading Main.tscn..." - it got here but the scene never finished # loading # "Could not load ..." - the load returned nothing # "Starting Main.tscn..." - loaded and the change reported OK, yet the # menu is somehow still up # "Could not start (...)" - the change refused, with the error number # # The scene is also loaded HERE rather than left to change_scene_to_file(), # which loads internally and reports a single error for both steps. Doing it # in two makes the failure specific, and takes whatever that call does about # background loading out of the picture - the web export is built without # thread support, so anything expecting a loader thread is worth removing # from the path rather than trusting. func _start(scene_path: String) -> void: status_label.text = "Loading %s..." % scene_path.get_file() var packed: PackedScene = load(scene_path) if packed == null: status_label.text = "Could not load %s" % scene_path.get_file() return status_label.text = "Starting %s..." % scene_path.get_file() var result: int = get_tree().change_scene_to_packed(packed) if result != OK: status_label.text = "Could not start (%s, error %d)" % [scene_path.get_file(), result] func _on_dev_mode_pressed() -> void: # Guarded as well as hidden. Visibility is presentation; this is the rule. if not is_developer_build(): status_label.text = "Dev mode only runs from Godot" return GameState.start_dev_game() get_tree().change_scene_to_file("res://Main.tscn") func _on_editor_pressed() -> void: if not is_developer_build(): status_label.text = "Map editor only runs from Godot" return if editor_button.enabled: _start("res://PlanetSetupEditor.tscn") return status_label.text = "Map editor coming soon"