Files
Steven Wroblewski 9921aba63a Kennungen, Kachelformen und Platzhalter für die Grafik
Jede Grafik auf dem Spielfeld bekommt eine Kennung. Art zeichnet sie im
gewählten Stil oder aus einem Bild gleichen Namens unter game/art.
ArtStyle zeichnet vorerst schlichte Platzhalter. TileShape beschreibt
die Nachbarn einer Kachel, damit Felsmassen außen rund und innen
geschlossen gezeichnet werden können. Die drei Stile Warm, Pastell und
Glimmen gibt es schon mit Namen, noch ohne eigene Zeichnungen.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-10-02 11:24:45 +02:00

193 lines
6.8 KiB
GDScript

class_name ArtStyle
extends RefCounted
## One look of the playing field. Draws every graphic that Art names, with
## code. This base class draws plain placeholders; each style overrides the
## drawing of every id.
##
## The entries of `state`, by id:
## - GROUND, SOFT_ROCK, HARD_ROCK, BEDROCK, DEAD_SOIL and LIVING_SOIL:
## `solid`, a TileShape mask of the neighbours that are rock or the edge of
## the map; `living`, for LIVING_SOIL, the mask of living neighbours;
## `variant`, from TileShape.variant
## - CRACKS: `hits`, 1 or more; `variant`
## - HIT: `age`, from 0 when the pickaxe lands to 1 when the burst is over;
## `variant`
## - FIGURE: `player`, the player's number or -1; `facing`, 1.0 to the
## right or -1.0 to the left; `step`, an angle that grows while the figure
## walks; `moving`
## - MARKER: `reachable`
## - MOSS: `variant`; `time`, in seconds
## - VINE: `points`, the path of the vine; `time`
## - FIBER_PLANT: `growth`, from 0 (a seed) to 1 (grown); `time`
## - GRAZER: `hop`, from 0 to 1 during one hop; `facing`
## - RUIN: `awake`, from 0 (asleep) to 1 (awake); `time`
## - STONE: nothing
## The name of the style on the command line and in the settings. The base
## class is no style to choose and has none.
func key() -> StringName:
return &""
## The key of the style's name in the texts, or an empty text for the base
## class.
func title_key() -> String:
return ""
## Draws the graphic `id` into `rect` on `canvas`, while `canvas` draws.
func draw(canvas: CanvasItem, id: StringName, rect: Rect2, state: Dictionary) -> void:
match id:
Art.GROUND:
_ground(canvas, rect, state)
Art.SOFT_ROCK:
_soft_rock(canvas, rect, state)
Art.HARD_ROCK:
_hard_rock(canvas, rect, state)
Art.BEDROCK:
_bedrock(canvas, rect, state)
Art.CRACKS:
_cracks(canvas, rect, state)
Art.HIT:
_hit(canvas, rect, state)
Art.FIGURE:
_figure(canvas, rect, state)
Art.MARKER:
_marker(canvas, rect, state)
Art.DEAD_SOIL:
_dead_soil(canvas, rect, state)
Art.LIVING_SOIL:
_living_soil(canvas, rect, state)
Art.MOSS:
_moss(canvas, rect, state)
Art.VINE:
_vine(canvas, rect, state)
Art.FIBER_PLANT:
_fiber_plant(canvas, rect, state)
Art.STONE:
_stone(canvas, rect, state)
Art.GRAZER:
_grazer(canvas, rect, state)
Art.RUIN:
_ruin(canvas, rect, state)
_:
push_error("no drawing for the graphic %s" % id)
## The colour of the figure of `player`. Players keep their colour; -1, a
## player not in the world yet, gets the first.
func player_color(player: int) -> Color:
return Color(0.95, 0.85, 0.6) if player <= 0 else Color(0.55, 0.8, 0.95)
func _ground(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.42, 0.36, 0.28))
func _soft_rock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.56, 0.55, 0.52))
func _hard_rock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.33, 0.33, 0.37))
func _bedrock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.12, 0.12, 0.14))
func _cracks(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var hits: int = state.get("hits", 1)
var inset := rect.grow(-3.0)
for line in hits:
var part := (line + 1.0) / (hits + 1.0)
var from := Vector2(inset.position.x, inset.position.y + inset.size.y * part)
var to := Vector2(inset.end.x, inset.position.y + inset.size.y * (1.0 - part))
canvas.draw_line(from, to, Color(0.1, 0.08, 0.06, 0.8), 1.0)
func _hit(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var age: float = state.get("age", 0.0)
for chip in 4:
var direction := Vector2.RIGHT.rotated(TAU * chip / 4.0 + 0.4)
var at := rect.get_center() + direction * rect.size.x * 0.6 * age
canvas.draw_rect(Rect2(at - Vector2(1, 1), Vector2(2, 2)), Color(0.8, 0.8, 0.75, 1.0 - age))
func _figure(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var radius := rect.size.x * 0.3
canvas.draw_circle(rect.get_center(), radius + 1.0, Color(0.15, 0.1, 0.05))
canvas.draw_circle(rect.get_center(), radius, player_color(state.get("player", -1)))
func _marker(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var color := Color(0.4, 1.0, 0.4) if state.get("reachable", false) else Color(1.0, 0.4, 0.4)
canvas.draw_rect(rect, color, false, 1.0)
func _dead_soil(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.45, 0.42, 0.38))
func _living_soil(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.35, 0.5, 0.25))
func _moss(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_circle(rect.get_center(), rect.size.x * 0.3, Color(0.3, 0.65, 0.3))
func _vine(canvas: CanvasItem, _rect: Rect2, state: Dictionary) -> void:
var points: PackedVector2Array = state.get("points", PackedVector2Array())
if points.size() >= 2:
canvas.draw_polyline(points, Color(0.25, 0.6, 0.25), 2.0)
func _fiber_plant(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var growth: float = state.get("growth", 1.0)
var foot := Vector2(rect.get_center().x, rect.end.y)
canvas.draw_line(foot, foot - Vector2(0, rect.size.y * growth), Color(0.4, 0.7, 0.3), 2.0)
func _stone(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void:
canvas.draw_circle(rect.get_center(), rect.size.x * 0.4, Color(0.6, 0.6, 0.58))
func _grazer(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
var lift := sin(PI * float(state.get("hop", 0.0))) * rect.size.y * 0.3
canvas.draw_circle(rect.get_center() - Vector2(0, lift), rect.size.x * 0.35, Color(0.92, 0.9, 0.85))
func _ruin(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void:
canvas.draw_rect(rect, Color(0.5, 0.5, 0.48))
var awake: float = state.get("awake", 0.0)
canvas.draw_circle(rect.get_center(), rect.size.x * 0.2, Color(1.0, 0.85, 0.3, awake))
# Helpers for the styles.
## A share from 0 to 1 that is the same for the same `variant` and `salt`.
static func share(variant: int, salt: int) -> float:
return float(TileShape.variant(Vector2i(variant & 0xffff, salt)) % 10007) / 10007.0
## A gentle swing from -1 to 1, slow and different for every `variant`.
static func sway(time: float, variant: int, speed := 1.3) -> float:
return sin(time * speed + share(variant, 7) * TAU)
## The point at `part` of the way along `points`, from 0 to 1.
static func along(points: PackedVector2Array, part: float) -> Vector2:
var total := 0.0
for i in points.size() - 1:
total += points[i].distance_to(points[i + 1])
var left := clampf(part, 0.0, 1.0) * total
for i in points.size() - 1:
var length := points[i].distance_to(points[i + 1])
if left <= length:
return points[i].lerp(points[i + 1], left / length if length > 0.0 else 0.0)
left -= length
return points[points.size() - 1]