Vitrine der Stilproben
Das Startmenü führt zu einer Vitrine, die den gewählten Stil an einer kleinen Szene zeigt: Fels, toter und lebendiger Boden, Lebensmoos, eine Ranke, die Steine in eine erwachende Ruine trägt, eine wachsende Faserpflanze, ein hüpfendes Weidetier und zwei Spielfiguren. Knöpfe, die Tasten 1 bis 3 und F2 wechseln den Stil, Esc führt zurück ins Menü. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user