extends Control ## Level map: a tile per level with its best stars. Levels up to the saved one can be ## played (replays keep the best stars); a few locked ones show what is ahead, and a dot ## marks levels where a new power or segment unlocks. const COLUMNS := 5 const LOCKED_AHEAD := 10 # locked tiles shown past the saved level const TILE := Vector2(88, 96) var _saved := 0 var _unlocks := {} # level -> Array of { name, color } @onready var grid: GridContainer = $Scroll/Margin/Grid @onready var scroll: ScrollContainer = $Scroll @onready var info: Label = $Info func _ready() -> void: Music.play("menu") _saved = Progress.load_level()[0] $Header/Totals/Count.text = str(Progress.total_stars()) $Header/Home.pressed.connect(func() -> void: get_tree().change_scene_to_file("res://scenes/title.tscn")) _collect_unlocks() grid.columns = COLUMNS var last := maxi(_saved, Progress.highest_won()) + LOCKED_AHEAD var current: Control for level in last + 1: var tile := _make_tile(level) grid.add_child(tile) if level == _saved: current = tile info.text = "Tap a level to play it. Replays keep your best stars." # Wait for layout, then centre the current level. await get_tree().process_frame await get_tree().process_frame scroll.scroll_vertical = int(current.position.y + TILE.y / 2.0 - scroll.size.y / 2.0) func _collect_unlocks() -> void: for info_dict: Dictionary in PowerUp.KINDS.values() + Block.KINDS.values(): var unlock: int = info_dict.unlock if unlock > 0: if not _unlocks.has(unlock): _unlocks[unlock] = [] _unlocks[unlock].append(info_dict) func _make_tile(level: int) -> Button: var playable := level <= _saved var tile := Button.new() tile.custom_minimum_size = TILE tile.focus_mode = Control.FOCUS_NONE var style := _style(level) for state in ["normal", "hover", "pressed", "disabled"]: tile.add_theme_stylebox_override(state, style) tile.pressed.connect(_on_tile_pressed.bind(level)) var box := VBoxContainer.new() box.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) box.alignment = BoxContainer.ALIGNMENT_CENTER box.mouse_filter = Control.MOUSE_FILTER_IGNORE tile.add_child(box) var number := Label.new() number.text = str(level) number.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER number.add_theme_font_size_override("font_size", 30) var ink := Params.COLOR_BACK if level == _saved else Color(1, 1, 1, 1.0 if playable else 0.3) number.add_theme_color_override("font_color", ink) box.add_child(number) var stars := Stars.new() stars.star_size = 15.0 stars.spacing = 3.0 stars.filled = Progress.stars(level) stars.modulate.a = 1.0 if playable else 0.3 box.add_child(stars) for child in box.get_children(): child.mouse_filter = Control.MOUSE_FILTER_IGNORE if _unlocks.has(level): var dot := UnlockDot.new() dot.color = _unlocks[level][0].color dot.position = Vector2(TILE.x - 16, 8) tile.add_child(dot) return tile func _style(level: int) -> StyleBoxFlat: var style := StyleBoxFlat.new() style.set_corner_radius_all(14) style.anti_aliasing_size = 1.5 if level == _saved: style.bg_color = Params.COLOR_ORANGE elif level < _saved: style.bg_color = Color("#3e3f37") else: style.bg_color = Color("#30312b") return style func _on_tile_pressed(level: int) -> void: if level <= _saved: Progress.current_level = level get_tree().change_scene_to_file("res://scenes/game.tscn") return var names := [] for unlock: Dictionary in _unlocks.get(level, []): names.append(unlock.name) info.text = "Level %d is locked. Reach it to play" % level info.text += (": %s unlocks there." % ", ".join(names)) if not names.is_empty() else "." ## The coloured dot on a tile where something new unlocks. class UnlockDot: extends Control var color := Color.WHITE func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE size = Vector2(10, 10) func _draw() -> void: draw_circle(Vector2(5, 5), 5.0, color, true, -1.0, true)