Files
assets-generator/preview/scene_base.gd
2026-10-10 16:35:28 +02:00

308 lines
12 KiB
GDScript

extends Control
## Szenen-Mockup des Kampfbildschirms: Umgebung (Material, Licht, Schatten) plus Anordnung.
## Hintergrundebenen sind Kinder mit show_behind_parent; die Oberfläche zeichnet _draw().
const FONT_DIR := "/home/arbeit/projects/cards/assets/fonts/"
const ELEM := {"Feuer": Color("d8502a"), "Wasser": Color("3a78d8"), "Erde": Color("8a6a3a"), "Luft": Color("8ec0dc")}
const KW := {"Feuer": "Doppelschlag", "Wasser": "Regeneration", "Erde": "Spott", "Luft": "Flink"}
var font: FontFile
var bold: FontFile
var light_dir := Vector2(3, 6) # Schattenversatz = Lichtrichtung
var P := {} # Palette der Szene
func _init() -> void:
font = FontFile.new()
font.load_dynamic_font(FONT_DIR + "DejaVuSans.ttf")
bold = FontFile.new()
bold.load_dynamic_font(FONT_DIR + "DejaVuSans-Bold.ttf")
func _ready() -> void:
build_env()
# --- Umgebung: Texturen und Licht als Ebenen hinter der Oberfläche -------------------
func layer(tex: Texture2D, rect: Rect2, mod := Color.WHITE, additive := false, behind := true) -> TextureRect:
var t := TextureRect.new()
t.texture = tex
t.position = rect.position
t.size = rect.size
t.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
t.stretch_mode = TextureRect.STRETCH_SCALE
t.modulate = mod
t.show_behind_parent = behind
t.mouse_filter = MOUSE_FILTER_IGNORE
if additive:
var m := CanvasItemMaterial.new()
m.blend_mode = CanvasItemMaterial.BLEND_MODE_ADD
t.material = m
add_child(t)
return t
func noise_tex(w: int, h: int, kind: String, freq: float, seed := 1) -> ImageTexture:
var n := FastNoiseLite.new()
n.seed = seed
n.frequency = freq
match kind:
"cell":
n.noise_type = FastNoiseLite.TYPE_CELLULAR
n.cellular_return_type = FastNoiseLite.RETURN_DISTANCE2_SUB
"cellfill":
n.noise_type = FastNoiseLite.TYPE_CELLULAR
n.cellular_return_type = FastNoiseLite.RETURN_CELL_VALUE
_:
n.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
n.fractal_octaves = 4
return ImageTexture.create_from_image(n.get_image(w, h))
func radial_tex(inner: Color, outer: Color, size := 256) -> GradientTexture2D:
var g := Gradient.new()
g.set_color(0, inner)
g.set_color(1, outer)
var t := GradientTexture2D.new()
t.gradient = g
t.fill = GradientTexture2D.FILL_RADIAL
t.fill_from = Vector2(0.5, 0.5)
t.fill_to = Vector2(1.0, 0.5)
t.width = size
t.height = size
return t
func linear_tex(a: Color, b: Color, vertical := true) -> GradientTexture2D:
var g := Gradient.new()
g.set_color(0, a)
g.set_color(1, b)
var t := GradientTexture2D.new()
t.gradient = g
t.fill_to = Vector2(0, 1) if vertical else Vector2(1, 0)
return t
## Lichtfleck (additiv) an pos mit Radius r.
func light(pos: Vector2, r: float, col: Color, strength := 0.5) -> void:
layer(radial_tex(Color(col, strength), Color(col, 0)), Rect2(pos - Vector2(r, r), Vector2(r * 2, r * 2)), Color.WHITE, true)
func vignette_layer(strength := 0.7) -> void:
layer(radial_tex(Color(0, 0, 0, 0), Color(0, 0, 0, strength)), Rect2(Vector2(-200, -250), Vector2(1680, 1300)))
func build_env() -> void:
pass
# --- Grundformen -------------------------------------------------------------------
static func lighter(c: Color, d: float) -> Color:
return Color(minf(1, c.r + d), minf(1, c.g + d), minf(1, c.b + d), c.a)
static func darker(c: Color, f: float) -> Color:
return Color(c.r * f, c.g * f, c.b * f, c.a)
func rr(rect: Rect2, radius: int, col: Color) -> void:
var sb := StyleBoxFlat.new()
sb.bg_color = col
sb.set_corner_radius_all(radius)
draw_style_box(sb, rect)
func border(rect: Rect2, radius: int, col: Color, w: int) -> void:
var sb := StyleBoxFlat.new()
sb.draw_center = false
sb.border_color = col
sb.set_border_width_all(w)
sb.set_corner_radius_all(radius)
draw_style_box(sb, rect)
func shadow(rect: Rect2, radius: int, size: int, alpha := 0.5, offset := Vector2(9, 9)) -> void:
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0, 0, 0, 0)
sb.set_corner_radius_all(radius)
sb.shadow_color = Color(0, 0, 0, alpha)
sb.shadow_size = size
sb.shadow_offset = light_dir if offset == Vector2(9, 9) else offset
draw_style_box(sb, rect)
func glow(rect: Rect2, radius: int, col: Color, size: int, alpha := 0.55) -> void:
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0, 0, 0, 0)
sb.set_corner_radius_all(radius)
sb.shadow_color = Color(col, alpha)
sb.shadow_size = size
draw_style_box(sb, rect)
## Senkrechter Verlauf innerhalb einer Rundung: Streifen, Rahmen deckt die Kanten.
func grad(rect: Rect2, radius: int, top: Color, bottom: Color, steps := 24) -> void:
rr(rect, radius, top.lerp(bottom, 0.5))
var inner := rect.grow(-radius * 0.3)
var h := inner.size.y / steps
for i in steps:
var t := float(i) / (steps - 1)
var y := inner.position.y + i * h
var r := Rect2(inner.position.x, y, inner.size.x, h + 1)
if i == 0 or i == steps - 1:
r = r.grow_individual(-radius * 0.6, 0, -radius * 0.6, 0)
draw_rect(r, top.lerp(bottom, t))
## Innerer Schatten (Vertiefung).
func inset(rect: Rect2, radius: int, strength := 0.5) -> void:
for i in 5:
border(rect.grow(-i), maxi(0, radius - i), Color(0, 0, 0, strength * (0.22 - i * 0.04)), 1)
## Gravur: dunkle Linie mit heller Kante darunter.
func engrave(rect: Rect2, radius: int, dark: Color, lightc: Color) -> void:
border(Rect2(rect.position + Vector2(1, 1), rect.size), radius, lightc, 1)
border(rect, radius, dark, 1)
func circle(c: Vector2, r: float, col: Color) -> void:
draw_circle(c, r, col, true, -1, true)
func ring(c: Vector2, r: float, col: Color, w: float) -> void:
draw_arc(c, r, 0, TAU, 64, col, w, true)
## Metallring: dunkel außen, hell innen, Lichtkante oben links.
func metal_ring(c: Vector2, r: float, w: float, col: Color) -> void:
ring(c, r, darker(col, 0.45), w + 2)
ring(c, r, col, w)
draw_arc(c, r, PI * 1.05, PI * 1.75, 24, lighter(col, 0.3), w * 0.5, true)
draw_arc(c, r, PI * 0.1, PI * 0.7, 24, darker(col, 0.6), w * 0.5, true)
func txt(s: String, size: int, col: Color, pos: Vector2, anchor := "center", b := false) -> void:
var f := bold if b else font
var dim := f.get_string_size(s, HORIZONTAL_ALIGNMENT_LEFT, -1, size)
var p := pos
match anchor:
"center": p -= dim / 2
"midtop": p.x -= dim.x / 2
"midleft": p.y -= dim.y / 2
"midright": p -= Vector2(dim.x, dim.y / 2)
draw_string(f, Vector2(p.x, p.y + f.get_ascent(size)), s, HORIZONTAL_ALIGNMENT_LEFT, -1, size, col)
func txt_shadow(s: String, size: int, col: Color, pos: Vector2, anchor := "center", b := true) -> void:
txt(s, size, Color(0, 0, 0, 0.6), pos + Vector2(1, 2), anchor, b)
txt(s, size, col, pos, anchor, b)
func txt_outlined(s: String, size: int, col: Color, pos: Vector2, ow := 4, ocol := Color(0.05, 0.04, 0.03, 0.95)) -> void:
var dim := bold.get_string_size(s, HORIZONTAL_ALIGNMENT_LEFT, -1, size)
var at := pos - dim / 2 + Vector2(0, bold.get_ascent(size))
draw_string_outline(bold, at, s, HORIZONTAL_ALIGNMENT_LEFT, -1, size, ow, ocol)
draw_string(bold, at, s, HORIZONTAL_ALIGNMENT_LEFT, -1, size, col)
func fit(s: String, size: int, max_w: float) -> String:
while bold.get_string_size(s, HORIZONTAL_ALIGNMENT_LEFT, -1, size).x > max_w and s.length() > 3:
s = s.substr(0, s.length() - 2) + "…"
return s
# --- Symbole -----------------------------------------------------------------------
func glyph(elem: String, c: Vector2, s: float, col: Color, outline_col = null) -> void:
if outline_col != null:
for o in [Vector2(-2, 0), Vector2(2, 0), Vector2(0, -2), Vector2(0, 2)]:
_glyph(elem, c + o, s, outline_col)
_glyph(elem, c, s, col)
func _glyph(elem: String, c: Vector2, s: float, col: Color) -> void:
var h := s / 2
match elem:
"Feuer":
for i in 3:
var off := Vector2((i - 1) * s * 0.3, (0 if i == 1 else s * 0.14))
var hh := h * (1.0 if i == 1 else 0.62)
var w := hh * 0.7
draw_colored_polygon(PackedVector2Array([c + off + Vector2(0, -hh), c + off + Vector2(w * 0.6, -hh * 0.2), c + off + Vector2(w * 0.42, hh * 0.5),
c + off + Vector2(0, hh), c + off + Vector2(-w * 0.42, hh * 0.5), c + off + Vector2(-w * 0.6, -hh * 0.2)]), col)
draw_colored_polygon(PackedVector2Array([c + Vector2(0, -h * 0.3), c + Vector2(h * 0.18, h * 0.4), c + Vector2(0, h * 0.8), c + Vector2(-h * 0.18, h * 0.4)]), lighter(col, 0.35))
"Wasser":
for i in 2:
var y := c.y + (i - 0.5) * s * 0.32
for k in 3:
var x := c.x - s * 0.38 + k * s * 0.38
draw_arc(Vector2(x, y), s * 0.19, PI, TAU, 16, col, maxf(2.5, s * 0.1), true)
"Erde":
for i in 3:
var off := (i - 1) * s * 0.38
var hs := h * (1.0 if i == 1 else 0.6)
draw_colored_polygon(PackedVector2Array([c + Vector2(off - hs, h * 0.7), c + Vector2(off, -hs), c + Vector2(off + hs, h * 0.7)]), col)
draw_colored_polygon(PackedVector2Array([c + Vector2(0, -h), c + Vector2(h * 0.3, -h * 0.45), c + Vector2(-h * 0.08, -h * 0.4)]), lighter(col, 0.3))
"Luft":
for i in 3:
var r := h * (1.0 - i * 0.28)
draw_arc(c + Vector2(i * s * 0.06, 0), r, PI * 0.2 + i * 0.4, PI * 1.5 + i * 0.4, 24, col, maxf(2.5, s * 0.1), true)
func keyword_icon(c: Vector2, r: float, kind: String, col: Color, bg := Color(0.05, 0.04, 0.03, 0.8)) -> void:
circle(c, r + 3, bg)
var w := maxf(1.5, r * 0.22)
match kind:
"Doppelschlag":
draw_line(c + Vector2(-r * 0.65, r * 0.65), c + Vector2(r * 0.6, -r * 0.6), col, w, true)
draw_line(c + Vector2(r * 0.65, r * 0.65), c + Vector2(-r * 0.6, -r * 0.6), col, w, true)
"Regeneration":
draw_line(c + Vector2(0, -r * 0.7), c + Vector2(0, r * 0.7), col, w * 1.5, true)
draw_line(c + Vector2(-r * 0.7, 0), c + Vector2(r * 0.7, 0), col, w * 1.5, true)
"Spott":
draw_line(c + Vector2(0, -r * 0.7), c + Vector2(0, r * 0.2), col, w * 1.6, true)
circle(c + Vector2(0, r * 0.6), w * 0.95, col)
"Flink":
for i in 2:
var x := c.x - r * 0.55 + i * r * 0.55
draw_polyline(PackedVector2Array([Vector2(x, c.y - r * 0.6), Vector2(x + r * 0.5, c.y), Vector2(x, c.y + r * 0.6)]), col, w, true)
## Wertmarke: Kreis mit Metallring und Zahl.
func badge(c: Vector2, r: float, value: String, col: Color, fsize: int, ring_col: Color) -> void:
circle(c + Vector2(1, 2), r + 2, Color(0, 0, 0, 0.45))
circle(c, r, col)
circle(c + Vector2(-r * 0.25, -r * 0.3), r * 0.5, Color(1, 1, 1, 0.18))
metal_ring(c, r, 2.5, ring_col)
txt_outlined(value, fsize, Color.WHITE, c, 3)
## Kostenkristall: Sechseck mit Facette.
func crystal(c: Vector2, r: float, cost: String, fsize: int, col: Color, ring_col: Color) -> void:
var pts := PackedVector2Array()
for i in 6:
var a := -PI / 2 + i * PI / 3
pts.append(c + Vector2(cos(a), sin(a)) * r)
var closed := pts.duplicate()
closed.append(pts[0])
draw_colored_polygon(pts, col)
draw_colored_polygon(PackedVector2Array([pts[4], pts[5], pts[0], c + Vector2(0, -r * 0.1), c + Vector2(-r * 0.55, r * 0.15)]), Color(1, 1, 1, 0.22))
draw_polyline(closed, darker(ring_col, 0.5), 4, true)
draw_polyline(closed, ring_col, 2, true)
txt_outlined(cost, fsize, Color.WHITE, c, 3)
## Namensband mit eingekerbten Enden.
func ribbon(rect: Rect2, col: Color, edge: Color) -> void:
var p := rect.position
var e := rect.end
var n := rect.size.y * 0.35
var pts := PackedVector2Array([Vector2(p.x, p.y), Vector2(e.x, p.y), Vector2(e.x - n, rect.get_center().y), Vector2(e.x, e.y), Vector2(p.x, e.y), Vector2(p.x + n, rect.get_center().y)])
draw_colored_polygon(PackedVector2Array([Vector2(p.x + 1, p.y + 2), Vector2(e.x + 1, p.y + 2), Vector2(e.x - n + 1, rect.get_center().y + 2), Vector2(e.x + 1, e.y + 2), Vector2(p.x + 1, e.y + 2), Vector2(p.x + n + 1, rect.get_center().y + 2)]), Color(0, 0, 0, 0.4))
draw_colored_polygon(pts, col)
var closed := pts.duplicate()
closed.append(pts[0])
draw_polyline(closed, edge, 1.5, true)