class_name Showcase extends CanvasLayer ## The showcase of the style samples: one small scene in the chosen style, ## with the terrain of the test map and things of later milestones. Dead ## soil turns living, moss glows, a vine carries stones into a waking ruin, a ## plant grows and an animal hops. Buttons switch the style. ## The player wants to go back to the menu. signal closed ## The scene, one letter per tile: B edge of the map, S soft rock, H hard ## rock, . dead soil, L living soil. const GRID: Array[String] = [ "BBSSSHHH............", "BSSSHHH.............", "SS...H..............", "..........LLLLLL....", ".......LLLLLLLLLLLL.", ".....LLLLLLLLLLLLLL.", ".....LLLLLLLLLLLLLL.", ".......LLLLLLLLLLL..", ".........LLLLLL.....", "....................", ] ## Pixels of the scene per pixel on screen. const ZOOM := 3.0 ## Seconds from seed to grown plant, and the pause before it starts again. const GROW_SECONDS := 6.0 const GROWN_SECONDS := 2.0 ## Seconds between two hops, and how long a hop lasts. const HOP_EVERY := 1.2 const HOP_SECONDS := 0.5 ## How far stones travel along the vine per second, as a share of its length. const STONE_SPEED := 0.1 const STONES := 3 ## The path of the vine, in tiles: along the living soil into the ruin. const VINE: Array[Vector2] = [ Vector2(6.8, 6.9), Vector2(9.0, 7.3), Vector2(11.0, 6.9), Vector2(13.0, 7.1), Vector2(15.0, 6.4), Vector2(17.3, 5.7), ] ## Where things stand, in tiles: the middle of their foot; for the figures ## the middle of the tile they stand on. const MOSS: Array[Vector2] = [Vector2(8.0, 4.6), Vector2(9.8, 3.3), Vector2(14.2, 3.4), Vector2(16.8, 6.8)] const PLANT := Vector2(10.9, 4.4) const GRAZER_HOME := Vector2(12.8, 5.2) ## The ruin covers three by three tiles from here. const RUIN := Vector2i(16, 3) ## The figures of two players: the own one and one of another colour. const FIGURES: Array[Vector2] = [Vector2(3.0, 5.0), Vector2(1.6, 6.6)] var art: Art ## Seconds since the showcase opened. var time := 0.0 var style_buttons: Array[Button] = [] var back_button := Button.new() var _view := ShowcaseView.new() var _title := Label.new() var _captions: Array[Label] = [] var _grid_codes := PackedByteArray() var _solid := PackedByteArray() var _living := PackedByteArray() ## The node that draws the scene. class ShowcaseView extends Node2D: var showcase: Showcase func _draw() -> void: showcase.draw_scene(self) func _ready() -> void: _read_grid() var background := ColorRect.new() background.color = Color(0.08, 0.08, 0.09) background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) add_child(background) _view.showcase = self _view.scale = Vector2(ZOOM, ZOOM) add_child(_view) _title.add_theme_font_size_override("font_size", 28) _title.position = Vector2(32, 20) add_child(_title) var row := HBoxContainer.new() row.position = Vector2(32, 660) row.add_theme_constant_override("separation", 12) add_child(row) for index in art.styles.size(): var button := Button.new() button.text = "%d %s" % [index + 1, tr(art.styles[index].title_key())] button.pressed.connect(func() -> void: art.select(art.styles[index].key())) style_buttons.append(button) row.add_child(button) back_button.text = tr("SHOWCASE_BACK") back_button.pressed.connect(func() -> void: closed.emit()) row.add_child(back_button) _add_captions() art.changed.connect(_on_style_changed) _on_style_changed() get_viewport().size_changed.connect(_layout) _layout() func _process(delta: float) -> void: time += delta _view.queue_redraw() func _unhandled_input(event: InputEvent) -> void: if not visible: return for index in mini(3, art.styles.size()): if event.is_action_pressed("style_%d" % (index + 1)): art.select(art.styles[index].key()) if event.is_action_pressed("next_style"): art.select_next() elif event.is_action_pressed("back"): closed.emit() ## How far the plant has grown at `seconds`, from 0 (a seed) to 1, over and ## over again. static func growth_at(seconds: float) -> float: return clampf(fposmod(seconds, GROW_SECONDS + GROWN_SECONDS) / GROW_SECONDS, 0.0, 1.0) ## How far the animal is through its hop at `seconds`: from 0 when it takes ## off to 1 when it lands, then 1 while it rests. static func hop_at(seconds: float) -> float: return clampf(fposmod(seconds, HOP_EVERY) / HOP_SECONDS, 0.0, 1.0) ## Where the animal is at `seconds`, in tiles: it hops around its home. static func grazer_at(seconds: float) -> Vector2: var hops := floorf(seconds / HOP_EVERY) + hop_at(seconds) return GRAZER_HOME + Vector2(cos(hops * 0.9), sin(hops * 0.9) * 0.4) ## How far each stone is along the vine at `seconds`, from 0 to 1. static func stones_at(seconds: float) -> Array[float]: var shares: Array[float] = [] for stone in STONES: shares.append(fposmod(seconds * STONE_SPEED + float(stone) / STONES, 1.0)) return shares ## How awake the ruin is at `seconds`: it wakes and dozes in slow breaths. static func awake_at(seconds: float) -> float: return 0.65 + 0.35 * sin(seconds * 1.2) ## The texts under the things in the scene, as keys. Tests read them. func caption_keys() -> PackedStringArray: var keys := PackedStringArray() for label in _captions: keys.append(label.get_meta("key")) return keys ## Draws the whole scene onto `canvas`, while it draws. func draw_scene(canvas: CanvasItem) -> void: if art == null: return var size := GridMotion.TILE_SIZE var width := GRID[0].length() var height := GRID.size() for y in height: for x in width: var tile := Vector2i(x, y) var rect := Rect2(Vector2(tile) * size, Vector2(size, size)) var code := _grid_codes[y * width + x] var state := { "solid": TileShape.mask(_solid, width, height, tile, false), "living": TileShape.mask(_living, width, height, tile, false), "variant": TileShape.variant(tile), } art.draw(canvas, _id_of(code), rect, state) for at in MOSS: art.draw(canvas, Art.MOSS, _tile_rect(at), {"variant": TileShape.variant(Vector2i(at)), "time": time}) var path := PackedVector2Array() for point in VINE: path.append(point * size) art.draw(canvas, Art.VINE, _bounds(path), {"points": path, "time": time}) for share in stones_at(time): var at := ArtStyle.along(path, share) art.draw(canvas, Art.STONE, Rect2(at - Vector2(4, 5), Vector2(8, 8))) art.draw(canvas, Art.FIBER_PLANT, _tile_rect(PLANT, Vector2(1.0, 1.8)), {"growth": growth_at(time), "time": time}) var grazer := grazer_at(time) var heading := grazer_at(time + 0.1) - grazer art.draw(canvas, Art.GRAZER, _tile_rect(grazer), {"hop": hop_at(time), "facing": 1.0 if heading.x >= 0.0 else -1.0}) var ruin := Rect2(Vector2(RUIN) * size, Vector2(3, 3) * size) art.draw(canvas, Art.RUIN, ruin, {"awake": awake_at(time), "time": time}) for player in FIGURES.size(): var at := (FIGURES[player] + Vector2(0.5, 0.5)) * size art.draw(canvas, Art.FIGURE, Avatar.figure_rect(at), {"player": player, "facing": 1.0, "step": time * 4.0 + player, "moving": true}) func _read_grid() -> void: var codes := {".": 0, "L": 1, "S": 2, "H": 3, "B": 4} _grid_codes.clear() for line in GRID: for letter in line: _grid_codes.append(codes[letter]) _solid = TileShape.flags(_grid_codes, [2, 3, 4]) _living = TileShape.flags(_grid_codes, [1]) static func _id_of(code: int) -> StringName: return [Art.DEAD_SOIL, Art.LIVING_SOIL, Art.SOFT_ROCK, Art.HARD_ROCK, Art.BEDROCK][code] ## A square of one tile, or of `size` tiles, whose bottom middle stands at ## `at` (in tiles) plus half a tile. static func _tile_rect(at: Vector2, size := Vector2.ONE) -> Rect2: var tile := GridMotion.TILE_SIZE var foot := (at + Vector2(0.5, 1.0)) * tile return Rect2(foot - Vector2(size.x * 0.5, size.y) * tile, size * tile) static func _bounds(points: PackedVector2Array) -> Rect2: var rect := Rect2(points[0], Vector2.ZERO) for point in points: rect = rect.expand(point) return rect func _add_captions() -> void: var entries := [ ["SHOWCASE_ROCK", Vector2(4.0, 3.1)], ["SHOWCASE_DEAD_SOIL", Vector2(2.6, 8.8)], ["SHOWCASE_LIVING_SOIL", Vector2(12.5, 9.15)], ["SHOWCASE_MOSS", Vector2(8.4, 5.6)], ["SHOWCASE_PLANT", Vector2(11.4, 2.3)], ["SHOWCASE_GRAZER", Vector2(13.3, 6.3)], ["SHOWCASE_VINE", Vector2(10.6, 7.7)], ["SHOWCASE_RUIN", Vector2(17.5, 1.9)], ["SHOWCASE_FIGURES", Vector2(2.6, 7.7)], ] for entry: Array in entries: var label := Label.new() label.text = tr(entry[0]) label.set_meta("key", entry[0]) label.set_meta("at", entry[1]) label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.add_theme_font_size_override("font_size", 13) label.add_theme_color_override("font_outline_color", Color(0.05, 0.05, 0.06)) label.add_theme_constant_override("outline_size", 5) _captions.append(label) add_child(label) ## Puts the scene in the middle of the screen and the texts under their ## things. func _layout() -> void: var scene_size := Vector2(GRID[0].length(), GRID.size()) * GridMotion.TILE_SIZE * ZOOM var screen := get_viewport().get_visible_rect().size _view.position = ((screen - scene_size) / 2.0).floor() for label in _captions: var at: Vector2 = label.get_meta("at") label.size = Vector2(160, 20) label.position = _view.position + at * GridMotion.TILE_SIZE * ZOOM - Vector2(80, 0) func _on_style_changed() -> void: _title.text = tr("SHOWCASE_TITLE") % tr(art.style.title_key()) for index in style_buttons.size(): style_buttons[index].disabled = art.styles[index] == art.style _view.queue_redraw()