This commit is contained in:
team3
2026-10-10 16:35:28 +02:00
commit b9ff5c1e64
33 changed files with 1705 additions and 0 deletions

1
preview/.godot/.gdignore Normal file
View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,11 @@
78098b89c88a74ebe8c20117b8754620
::res://::1791633325
mockup_base.gd::GDScript::702225824222732329::1791633325::0::1::::<>Control<><>0<>0<><>::
mockup_comic.gd::GDScript::4677893705596550348::1791633325::0::1::::<>Control<><>0<>0<><>::
mockup_glas.gd::GDScript::8582932834366478212::1791633325::0::1::::<>Control<><>0<>0<><>::
mockup_metall.gd::GDScript::6860315249431576206::1791633325::0::1::::<>Control<><>0<>0<><>::
mockup_stein.gd::GDScript::1706979404678058972::1791633325::0::1::::<>Control<><>0<>0<><>::
README.md::TextFile::-1::1791632797::0::1::::<><><>0<>0<><>::
render_svg.gd::GDScript::2096850713855216195::1791632731::0::1::::<>SceneTree<><>0<>0<><>::
shot.gd::GDScript::5062259463454179561::1791632877::0::1::::<>SceneTree<><>0<>0<><>::
::res://out/::1791632797

View File

@@ -0,0 +1,7 @@
[game_view]
select_mode=0
[editor_metadata]
executable_path="/home/arbeit/bin/godot"

View File

@@ -0,0 +1 @@
list=[]

Binary file not shown.

15
preview/README.md Normal file
View File

@@ -0,0 +1,15 @@
# Preview
Godot-Projekt zum Prüfen von Grafik, bevor sie ins Spiel kommt.
- `render_svg.gd`: SVG → PNG über Godots Importer. Zeigt, was Godot aus einem SVG macht.
`godot --headless --path preview -s render_svg.gd -- in.svg out.png [scale]`
- `shot.gd`: Mockup-Szene oder Control-Skript rendern und als PNG speichern. Braucht Fenster.
`godot --path preview -s shot.gd -- mockup.gd out.png`
- `scene_base.gd`: Bausteine für Szenen: Noise-Texturen, additives Licht, Vignette, Schatten,
Verläufe, Gravur, Metallringe, Marken, Kristalle, Bänder.
- `scene_*.gd`: Szenen je Richtung (Umgebung, Licht, Material, Anordnung).
- `mockup_*.gd`: ältere Mockups nur mit Rahmen und Farben.
- `out/`: fertige PNGs.
Godots SVG-Importer kann nicht: Text, Pattern, feDropShadow. Kann: Verläufe, Blur, Strokes, Opacity.

429
preview/mockup_base.gd Normal file
View File

@@ -0,0 +1,429 @@
extends Control
## Mockup des Kampfbildschirms (1280×800) von Dinger. Die Optik kommt aus dem Dictionary P;
## Richtungen überschreiben P in _init(). Layout wie BattleLayout im Spiel.
const FONT_DIR := "/home/arbeit/projects/cards/assets/fonts/"
const ELEM := {"Feuer": Color("be3c23"), "Wasser": Color("2d64be"), "Erde": Color("73552d"), "Luft": Color("78aac8")}
var font: FontFile
var bold: FontFile
var P := {
"bg_top": Color("1a1c24"), "bg_bottom": Color("12131a"), "vignette": 0.35,
"panel_bg": Color("282b36"), "panel_bg2": Color("282b36"), "panel_border": Color("3a3e4c"), "panel_border_w": 1,
"panel_radius": 8, "panel_shadow": 6, "panel_highlight": Color(1, 1, 1, 0.0), "chamfer": 0,
"slot_bg": Color(1, 1, 1, 0.03), "slot_border": Color(1, 1, 1, 0.08),
"card_radius": 8, "card_border": Color("14141c"), "card_border_w": 2, "card_inner": Color(1, 1, 1, 0.0),
"card_grad": 0.0, "card_vignette": 0.0, "glyph_shadow": true, "glyph_outline": false,
"band_bg": Color(0.05, 0.05, 0.08, 0.82), "band_text": Color("ececf0"), "band_inset": 0,
"back_bg": Color("3c285a"), "back_bg2": Color("3c285a"), "back_border": Color("f5c850"), "back_inner": Color("5a3c82"),
"stat": "circle", "stat_outline": Color("14141c"), "stat_w": 2,
"btn_bg": Color("3a3e4c"), "btn_bg2": Color("3a3e4c"), "btn_border": Color("9698a5"), "btn_w": 2, "btn_radius": 8, "btn_edge": Color(0, 0, 0, 0),
"text": Color("ececf0"), "muted": Color("9698a5"), "gold": Color("f5c850"), "life": Color("e65555"), "mana": Color("6eaaf0"),
"amber": Color("f0b429"), "blue": Color("4a86d8"), "crystal": Color("3a78d2"),
"rare": Color("c8d0dc"), "ultra": Color("f0aa28"), "elem_light": 0.38,
}
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")
# --- 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)
## Eckpunkte eines abgerundeten (oder abgeschrägten) Rechtecks.
func shape(rect: Rect2, radius: float) -> PackedVector2Array:
var pts := PackedVector2Array()
var cut: float = P.chamfer
if cut > 0:
var p := rect.position
var e := rect.end
pts.append_array([Vector2(p.x + cut, p.y), Vector2(e.x - cut, p.y), Vector2(e.x, p.y + cut), Vector2(e.x, e.y - cut),
Vector2(e.x - cut, e.y), Vector2(p.x + cut, e.y), Vector2(p.x, e.y - cut), Vector2(p.x, p.y + cut)])
return pts
radius = minf(radius, minf(rect.size.x, rect.size.y) / 2)
var centers := [rect.position + Vector2(radius, radius), Vector2(rect.end.x - radius, rect.position.y + radius),
rect.end - Vector2(radius, radius), Vector2(rect.position.x + radius, rect.end.y - radius)]
for i in 4:
for k in 13:
var a := -PI + PI / 2 * i + PI / 2 * k / 12
pts.append(centers[i] + Vector2(cos(a), sin(a)) * radius)
return pts
## Fläche: senkrechter Verlauf von top nach bottom.
func fill(rect: Rect2, radius: float, top: Color, bottom: Color) -> void:
var pts := shape(rect, radius)
var cols := PackedColorArray()
for p in pts:
cols.append(top.lerp(bottom, clampf((p.y - rect.position.y) / rect.size.y, 0, 1)))
draw_polygon(pts, cols)
func outline(rect: Rect2, radius: float, col: Color, w: float) -> void:
if P.chamfer > 0:
var pts := shape(rect.grow(-w / 2), radius - w / 2)
pts.append(pts[0])
draw_polyline(pts, col, w, true)
return
var sb := StyleBoxFlat.new()
sb.draw_center = false
sb.border_color = col
sb.set_border_width_all(int(round(w)))
sb.set_corner_radius_all(int(radius))
sb.anti_aliasing_size = 1.0
draw_style_box(sb, rect)
func shadow(rect: Rect2, radius: float, size: int, alpha := 0.45, offset := Vector2(0, 3)) -> void:
if size <= 0:
return
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0, 0, 0, 0)
sb.set_corner_radius_all(int(radius))
sb.shadow_color = Color(0, 0, 0, alpha)
sb.shadow_size = size
sb.shadow_offset = offset
draw_style_box(sb, rect)
func glow(rect: Rect2, radius: float, col: Color, size: int) -> void:
var sb := StyleBoxFlat.new()
sb.bg_color = Color(0, 0, 0, 0)
sb.set_corner_radius_all(int(radius))
sb.shadow_color = Color(col, 0.55)
sb.shadow_size = size
draw_style_box(sb, rect)
## Dunkler Rand nach innen (Vignette) als Ringe.
func vignette(rect: Rect2, radius: float, strength: float) -> void:
if strength <= 0:
return
for i in 4:
var r := rect.grow(-i * 3.0)
outline(r, radius - i * 3, Color(0, 0, 0, strength * (0.3 - i * 0.06)), 3)
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)
"topright": p.x -= dim.x
draw_string(f, Vector2(p.x, p.y + f.get_ascent(size)), s, HORIZONTAL_ALIGNMENT_LEFT, -1, size, col)
func txt_outlined(s: String, size: int, col: Color, pos: Vector2, ow := 4, ocol := Color(0.05, 0.05, 0.08, 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
# --- Bausteine -----------------------------------------------------------------
func panel(rect: Rect2, border = null) -> void:
shadow(rect, P.panel_radius, P.panel_shadow)
fill(rect, P.panel_radius, P.panel_bg, P.panel_bg2)
if P.panel_highlight.a > 0:
draw_line(rect.position + Vector2(P.panel_radius, 1.5), Vector2(rect.end.x - P.panel_radius, rect.position.y + 1.5), P.panel_highlight, 1.0)
var bc: Color = border if border != null else P.panel_border
if P.panel_border_w > 0:
outline(rect, P.panel_radius, bc, P.panel_border_w)
func button(rect: Rect2, label: String, size := 14) -> void:
if P.btn_edge.a > 0:
fill(Rect2(rect.position + Vector2(0, 4), rect.size), P.btn_radius, P.btn_edge, P.btn_edge)
else:
shadow(rect, P.btn_radius, 4, 0.35)
fill(rect, P.btn_radius, P.btn_bg, P.btn_bg2)
if P.panel_highlight.a > 0:
draw_line(rect.position + Vector2(P.btn_radius, 1.5), Vector2(rect.end.x - P.btn_radius, rect.position.y + 1.5), P.panel_highlight, 1.0)
if P.btn_w > 0:
outline(rect, P.btn_radius, P.btn_border, P.btn_w)
txt(label, size, P.text, rect.get_center())
func slot(rect: Rect2) -> void:
fill(rect, P.card_radius, P.slot_bg, P.slot_bg)
outline(rect, P.card_radius, P.slot_border, 1)
## Zahl auf Form: Kreis, Sechseck oder Schild.
func stat(c: Vector2, r: float, value: String, col: Color, fsize: int, shape_name := "") -> void:
var kind: String = shape_name if shape_name != "" else P.stat
if kind == "hex":
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.16))
draw_polyline(closed, P.stat_outline, P.stat_w + 1, true)
else:
draw_circle(c, r + P.stat_w, P.stat_outline, true, -1, true)
draw_circle(c, r, col, true, -1, true)
draw_circle(c + Vector2(-r * 0.25, -r * 0.3), r * 0.45, Color(1, 1, 1, 0.16), true, -1, true)
draw_arc(c, r - 0.5, 0, TAU, 40, darker(col, 0.75), 1.5, true)
txt_outlined(value, fsize, Color.WHITE, c, 3)
func crystal(c: Vector2, r: float, cost: String, fsize: int) -> void:
stat(c, r, cost, P.crystal, fsize, "hex")
func keyword_icon(c: Vector2, r: float, kind: String, col: Color) -> void:
draw_circle(c, r + 3, Color(0.05, 0.05, 0.08, 0.75))
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)
draw_line(c + Vector2(r * 0.65, r * 0.65), c + Vector2(-r * 0.6, -r * 0.6), col, w)
"Regeneration":
draw_line(c + Vector2(0, -r * 0.7), c + Vector2(0, r * 0.7), col, w * 1.5)
draw_line(c + Vector2(-r * 0.7, 0), c + Vector2(r * 0.7, 0), col, w * 1.5)
"Spott":
draw_line(c + Vector2(0, -r * 0.7), c + Vector2(0, r * 0.2), col, w * 1.6)
draw_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)
# --- Elementsymbole --------------------------------------------------------------
func glyph(elem: String, c: Vector2, s: float, alpha := 0.9) -> void:
var col := lighter(ELEM[elem], P.elem_light)
col.a = alpha
if P.glyph_outline:
for o in [Vector2(-2, 0), Vector2(2, 0), Vector2(0, -2), Vector2(0, 2), Vector2(2, 2), Vector2(-2, 2)]:
_glyph(elem, c + o, s, Color(0.04, 0.03, 0.06, alpha))
elif P.glyph_shadow:
_glyph(elem, c + Vector2(2, 3), s, Color(0, 0, 0, alpha * 0.45))
_glyph(elem, c, s, col)
_glyph_light(elem, c, s, Color(1, 1, 1, alpha * 0.35))
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.32, (0 if i == 1 else s * 0.12))
var hh := h * (1.0 if i == 1 else 0.68)
var w := hh * 0.75
draw_colored_polygon(PackedVector2Array([c + off + Vector2(0, -hh), c + off + Vector2(w * 0.55, -hh * 0.25), c + off + Vector2(w * 0.4, hh * 0.45),
c + off + Vector2(0, hh), c + off + Vector2(-w * 0.4, hh * 0.45), c + off + Vector2(-w * 0.55, -hh * 0.25)]), col)
"Wasser":
for i in 2:
var y := c.y + (i - 0.5) * s * 0.3
for k in 3:
var x := c.x - s * 0.4 + k * s * 0.4
draw_arc(Vector2(x, y), s * 0.2, PI, TAU, 12, col, maxf(2, s * 0.09), true)
"Erde":
for i in 3:
var off := (i - 1) * s * 0.4
var hs := h * (1.0 if i == 1 else 0.65)
draw_colored_polygon(PackedVector2Array([c + Vector2(off - hs, h * 0.7), c + Vector2(off, -hs), c + Vector2(off + hs, h * 0.7)]), col)
draw_line(c + Vector2(-h, h * 0.7), c + Vector2(h, h * 0.7), col, maxf(2, s * 0.08))
"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, 20, col, maxf(2, s * 0.09), true)
## Lichtkante: kleiner heller Kern.
func _glyph_light(elem: String, c: Vector2, s: float, col: Color) -> void:
var h := s / 2
match elem:
"Feuer":
var w := h * 0.75
draw_colored_polygon(PackedVector2Array([c + Vector2(0, -h * 0.35), c + Vector2(w * 0.25, h * 0.35), c + Vector2(0, h * 0.75), c + Vector2(-w * 0.25, h * 0.35)]), Color(1.0, 0.9, 0.5, col.a * 2))
"Erde":
draw_colored_polygon(PackedVector2Array([c + Vector2(0, -h), c + Vector2(h * 0.35, -h * 0.4), c + Vector2(-h * 0.1, -h * 0.4)]), col)
_:
pass
# --- Karten ------------------------------------------------------------------------
## Kartenfläche in Elementfarbe mit Rahmen; frame: Rahmenfarbe (Seltenheit) oder null.
func card_body(rect: Rect2, elem: String, frame = null, shadow_size := 6) -> void:
var base: Color = ELEM[elem]
var fc: Color = frame if frame != null else P.card_border
if frame != null:
glow(rect, P.card_radius, fc, 7)
else:
shadow(rect, P.card_radius, shadow_size)
fill(rect, P.card_radius, lighter(base, P.card_grad), darker(base, 1.0 - P.card_grad * 1.6))
vignette(rect, P.card_radius, P.card_vignette)
if P.card_inner.a > 0:
outline(rect.grow(-P.card_border_w - 1), P.card_radius - P.card_border_w, P.card_inner, 1)
outline(rect, P.card_radius, fc, P.card_border_w)
func name_band(rect: Rect2, y: float, h: float, name: String, fsize: int, col = null) -> void:
var band := Rect2(rect.position.x + P.band_inset, y, rect.size.x - P.band_inset * 2, h)
if P.band_inset > 0:
fill(band, h / 2, P.band_bg, P.band_bg)
else:
draw_rect(band, P.band_bg)
var c: Color = col if col != null else P.band_text
txt(fit(name, fsize, band.size.x - 10), fsize, c, band.get_center(), "center", true)
## Kleine Karte (Hand, Feld): 110×140.
func card_small(rect: Rect2, name: String, elem: String, cost: int, atk: int, hp: int, frame = null, name_col = null) -> void:
card_body(rect, elem, frame)
glyph(elem, rect.position + Vector2(rect.size.x / 2, rect.size.y * 0.35), rect.size.x * 0.56)
name_band(rect, rect.position.y + rect.size.y * 0.5, 22, name, 11, name_col)
stat(rect.position + Vector2(17, rect.size.y - 17), 11, str(atk), P.amber, 13)
stat(rect.position + Vector2(rect.size.x - 17, rect.size.y - 17), 11, str(hp), P.blue, 13)
crystal(rect.position + Vector2(17, 17), 11, str(cost), 13)
## Große Karte (Vorschau): 170×238, mit Eigenschaft.
func card_big(rect: Rect2, name: String, elem: String, cost: int, atk: int, hp: int, kw: String, frame = null, name_col = null) -> void:
card_body(rect, elem, frame, 10)
glyph(elem, rect.position + Vector2(rect.size.x / 2, rect.size.y * 0.2), rect.size.x * 0.56)
name_band(rect, rect.position.y + rect.size.y * 0.36, 26, name, 14, name_col)
var y := rect.position.y + rect.size.y * 0.36 + 40
keyword_icon(Vector2(rect.position.x + 40, y), 9, kw, lighter(ELEM[elem], 0.45))
txt(kw, 12, P.text, Vector2(rect.position.x + 56, y), "midleft")
var box := Rect2(rect.position.x + 8, y + 18, rect.size.x - 16, 54)
draw_rect(box, Color(0.06, 0.06, 0.09, 0.6))
txt("Greift zweimal je Zug", 11, P.text, box.get_center())
stat(rect.position + Vector2(22, rect.size.y - 22), 15, str(atk), P.amber, 17)
stat(rect.position + Vector2(rect.size.x - 22, rect.size.y - 22), 15, str(hp), P.blue, 17)
crystal(rect.position + Vector2(22, 22), 14, str(cost), 17)
func card_back(rect: Rect2, deg := 0.0) -> void:
var c := rect.get_center()
draw_set_transform(c, deg_to_rad(deg))
var r := Rect2(-rect.size / 2, rect.size)
shadow(r, P.card_radius, 6)
fill(r, P.card_radius, P.back_bg, P.back_bg2)
outline(r.grow(-9), P.card_radius - 4, P.back_inner, 2)
draw_circle(Vector2.ZERO, 22, Color(P.back_inner, 0.5), true, -1, true)
draw_arc(Vector2.ZERO, 22, 0, TAU, 48, P.back_border, 2, true)
outline(r, P.card_radius, P.back_border, P.card_border_w + 1)
txt("?", 30, P.back_border, Vector2(0, 0), "center", true)
draw_set_transform(Vector2.ZERO)
func deck(rect: Rect2, count: String, label := "") -> void:
for i in range(3, 0, -1):
var r := Rect2(rect.position + Vector2(i * 3, -i * 3), rect.size)
fill(r, P.card_radius, darker(P.back_bg, 0.8), darker(P.back_bg2, 0.8))
outline(r, P.card_radius, Color(0, 0, 0, 0.5), 1)
shadow(rect, P.card_radius, 6)
fill(rect, P.card_radius, P.back_bg, P.back_bg2)
outline(rect.grow(-9), P.card_radius - 4, P.back_inner, 2)
outline(rect, P.card_radius, Color(P.back_border, 0.6), 2)
if label != "":
txt(label, 17, P.muted, rect.get_center() + Vector2(0, -14), "center", true)
txt("◆ 0", 17, P.mana, rect.get_center() + Vector2(0, 14), "center", true)
else:
txt(count, 22, P.text, rect.get_center(), "center", true)
txt(count, 11, P.muted, Vector2(rect.get_center().x, rect.end.y - 22), "center")
func hero_box(rect: Rect2, name: String, life: String, border = null) -> void:
panel(rect, border)
txt(name, 17, P.text, Vector2(rect.get_center().x, rect.position.y + 12), "midtop", true)
txt("♥", 24, P.life, Vector2(rect.position.x + 24, rect.position.y + 46), "center", true)
txt(life, 24, P.life, Vector2(rect.position.x + 42, rect.position.y + 46), "midleft", true)
txt("◆", 20, P.mana, Vector2(rect.end.x - 54, rect.position.y + 46), "center", true)
txt("0", 24, P.mana, Vector2(rect.end.x - 14, rect.position.y + 46), "midright", true)
func element_row(rect: Rect2, elem: String, kw: String, filled: int, played: String) -> void:
var col: Color = ELEM[elem]
fill(rect, 6, Color(col, 0.16), Color(col, 0.10))
outline(rect, 6, Color(col, 0.45), 1)
txt(elem, 13, P.text, Vector2(rect.position.x + 10, rect.get_center().y), "midleft", true)
for k in 2:
keyword_icon(Vector2(rect.position.x + 86 + k * 24, rect.get_center().y), 8, kw, lighter(col, 0.45) if k < filled else Color(col, 0.35))
txt(played, 15, lighter(col, 0.45), Vector2(rect.position.x + 142, rect.get_center().y), "midleft", true)
# --- Hintergrund ---------------------------------------------------------------------
func background() -> void:
fill(Rect2(Vector2.ZERO, size), 0, P.bg_top, P.bg_bottom)
var c := Vector2(size.x * 0.5, size.y * 0.42)
for i in range(8, 0, -1):
draw_circle(c, size.x * 0.09 * i, Color(1, 1, 1, 0.012))
if P.vignette > 0:
for i in 6:
outline(Rect2(Vector2.ZERO, size).grow(-i * 14.0), 0, Color(0, 0, 0, P.vignette * 0.12), 28)
# --- Der Bildschirm ------------------------------------------------------------------
func _draw() -> void:
background()
# Gegner: Hand oben, Stapel und Kasten rechts
for i in 4:
var r := Rect2(410 + i * 120, 11, 110, 140)
var t := i - 1.5
card_back(Rect2(r.position + Vector2(0, -t * t * 3), r.size), -t * 3)
deck(Rect2(1090, 10, 130, 182), "35")
hero_box(Rect2(1050, 200, 210, 70), "Gegner", "35", P.mana)
button(Rect2(20, 48, 210, 32), "Überspringen", 13)
element_row(Rect2(20, 222, 210, 28), "Feuer", "Doppelschlag", 1, "1")
# Bretter
for j in 5:
var r := Rect2(345 + j * 120, 220, 110, 140)
if j == 0:
card_small(r, "Schwelender Drache", "Feuer", 1, 0, 5)
else:
slot(r)
for j in 5:
var r := Rect2(345 + j * 120, 440, 110, 140)
if j == 0:
card_small(r, "Rieselnder Geist", "Wasser", 1, 1, 2)
else:
slot(r)
element_row(Rect2(20, 442, 210, 28), "Wasser", "Regeneration", 1, "1")
# Hand
var hand := [["Leiser Hauch", "Luft", 0, 0, 1, null, null], ["Glimmender Wolf", "Feuer", 1, 1, 3, P.rare, lighter(P.rare, 0.25)],
["Stiller Tropfen", "Wasser", 0, 0, 1, null, null], ["Fester Kiesel", "Erde", 1, 1, 1, P.ultra, lighter(P.ultra, 0.25)]]
var total := hand.size() * 110 + (hand.size() - 1) * 10
for i in hand.size():
var h = hand[i]
var t := i - (hand.size() - 1) / 2.0
var r := Rect2(240 + (800 - total) / 2 + i * 120, 610 + t * t * 3, 110, 140)
draw_set_transform(r.get_center(), deg_to_rad(t * 3))
card_small(Rect2(-r.size / 2, r.size), h[0], h[1], h[2], h[3], h[4], h[5], h[6])
draw_set_transform(Vector2.ZERO)
# Eigener Kasten, Stapel, Vorschau
hero_box(Rect2(1050, 526, 210, 70), "Spieler", "30")
deck(Rect2(1090, 606, 130, 182), "35", "Ziehen")
card_big(Rect2(1070, 278, 170, 238), "Glimmender Wolf", "Feuer", 1, 1, 3, "Doppelschlag", P.rare, lighter(P.rare, 0.25))
txt("Dinger · Richtung: " + P.get("name", "?"), 12, P.muted, Vector2(20, 780), "midleft")

View File

@@ -0,0 +1 @@
uid://kafs6twkfjb2

19
preview/mockup_comic.gd Normal file
View File

@@ -0,0 +1,19 @@
extends "res://mockup_base.gd"
## Richtung Comic: kräftige Farben, dicke dunkle Outlines, große runde Formen. Nähe zu Clash Royale.
func _init() -> void:
super()
P.merge({
"name": "Comic",
"bg_top": Color("2a2244"), "bg_bottom": Color("140f22"), "vignette": 0.4,
"panel_bg": Color("3a3058"), "panel_bg2": Color("2a2242"), "panel_border": Color("0c0a14"), "panel_border_w": 3,
"panel_radius": 14, "panel_shadow": 0, "panel_highlight": Color(1, 1, 1, 0.16),
"slot_bg": Color(0, 0, 0, 0.25), "slot_border": Color("0c0a14"),
"card_radius": 14, "card_border": Color("0c0a14"), "card_border_w": 3, "card_inner": Color(1, 1, 1, 0.35),
"card_grad": 0.22, "card_vignette": 0.0, "glyph_outline": true,
"band_bg": Color("0c0a14"), "band_text": Color("ffffff"), "band_inset": 6,
"back_bg": Color("7a3cc8"), "back_bg2": Color("3a1a6a"), "back_border": Color("0c0a14"), "back_inner": Color("ffd25a"),
"stat": "circle", "stat_outline": Color("0c0a14"), "stat_w": 3,
"btn_bg": Color("6a5aa8"), "btn_bg2": Color("4a3a80"), "btn_border": Color("0c0a14"), "btn_w": 3, "btn_radius": 12, "btn_edge": Color("1a1430"),
"text": Color("ffffff"), "muted": Color("b0a8d0"), "gold": Color("ffd25a"), "elem_light": 0.5,
}, true)

View File

@@ -0,0 +1 @@
uid://b7wahxecyn8t1

19
preview/mockup_glas.gd Normal file
View File

@@ -0,0 +1,19 @@
extends "res://mockup_base.gd"
## Richtung Glas: kühl, dunkel, durchscheinende Flächen, feine helle Kanten, weiche Schatten.
func _init() -> void:
super()
P.merge({
"name": "Glas",
"bg_top": Color("131a2c"), "bg_bottom": Color("0a0e18"), "vignette": 0.3,
"panel_bg": Color(1, 1, 1, 0.07), "panel_bg2": Color(1, 1, 1, 0.04), "panel_border": Color(1, 1, 1, 0.16), "panel_border_w": 1,
"panel_radius": 12, "panel_shadow": 14, "panel_highlight": Color(1, 1, 1, 0.22),
"slot_bg": Color(1, 1, 1, 0.03), "slot_border": Color(1, 1, 1, 0.10),
"card_radius": 12, "card_border": Color(1, 1, 1, 0.22), "card_border_w": 1, "card_inner": Color(0, 0, 0, 0),
"card_grad": 0.06, "card_vignette": 0.6,
"band_bg": Color(0.02, 0.03, 0.06, 0.7), "band_text": Color("f4f6fa"), "band_inset": 6,
"back_bg": Color("1e2a4a"), "back_bg2": Color("0e1426"), "back_border": Color("6eaaf0"), "back_inner": Color("2e4070"),
"stat": "circle", "stat_outline": Color("0a0e18"), "stat_w": 1,
"btn_bg": Color(1, 1, 1, 0.10), "btn_bg2": Color(1, 1, 1, 0.05), "btn_border": Color(1, 1, 1, 0.2), "btn_w": 1, "btn_radius": 10,
"text": Color("f4f6fa"), "muted": Color("8e98b0"), "gold": Color("f5c850"),
}, true)

View File

@@ -0,0 +1 @@
uid://dujvwdfy6a637

19
preview/mockup_metall.gd Normal file
View File

@@ -0,0 +1,19 @@
extends "res://mockup_base.gd"
## Richtung Metall: kantig, abgeschrägte Ecken, Stahlverläufe, Sechsecke. Technisch, kühl.
func _init() -> void:
super()
P.merge({
"name": "Metall",
"bg_top": Color("161b24"), "bg_bottom": Color("090b10"), "vignette": 0.5,
"panel_bg": Color("2c3542"), "panel_bg2": Color("181e28"), "panel_border": Color("55627a"), "panel_border_w": 1,
"panel_radius": 2, "panel_shadow": 8, "panel_highlight": Color(0.8, 0.9, 1.0, 0.3), "chamfer": 8,
"slot_bg": Color(0, 0, 0, 0.3), "slot_border": Color("3a4454"),
"card_radius": 2, "card_border": Color("0d1016"), "card_border_w": 3, "card_inner": Color("8a98ad"),
"card_grad": 0.08, "card_vignette": 0.35,
"band_bg": Color("0d1016"), "band_text": Color("e8eef8"),
"back_bg": Color("3a4454"), "back_bg2": Color("161b24"), "back_border": Color("8a98ad"), "back_inner": Color("55627a"),
"stat": "hex", "stat_outline": Color("0d1016"), "stat_w": 2,
"btn_bg": Color("3e4a5c"), "btn_bg2": Color("222a36"), "btn_border": Color("6a7890"), "btn_w": 1, "btn_radius": 2,
"text": Color("e8eef8"), "muted": Color("8a98ad"), "gold": Color("f0c860"), "elem_light": 0.3,
}, true)

View File

@@ -0,0 +1 @@
uid://c40gniw2kyopy

19
preview/mockup_stein.gd Normal file
View File

@@ -0,0 +1,19 @@
extends "res://mockup_base.gd"
## Richtung Stein: warm, Holz und Bronze, Bevel-Rahmen. Nähe zu Hearthstone.
func _init() -> void:
super()
P.merge({
"name": "Stein",
"bg_top": Color("241a16"), "bg_bottom": Color("110c0a"), "vignette": 0.6,
"panel_bg": Color("3d2e24"), "panel_bg2": Color("2a1e18"), "panel_border": Color("8a6a3a"), "panel_border_w": 2,
"panel_radius": 10, "panel_shadow": 8, "panel_highlight": Color(1, 0.9, 0.7, 0.18),
"slot_bg": Color(0, 0, 0, 0.22), "slot_border": Color("5a4630"),
"card_radius": 10, "card_border": Color("1a120c"), "card_border_w": 3, "card_inner": Color("c9a25a"),
"card_grad": 0.14, "card_vignette": 0.5,
"band_bg": Color("1a120c"), "band_text": Color("f0d890"),
"back_bg": Color("5a2a22"), "back_bg2": Color("2e1410"), "back_border": Color("c9a25a"), "back_inner": Color("8a4a3a"),
"stat": "circle", "stat_outline": Color("1a120c"), "stat_w": 2,
"btn_bg": Color("5a4330"), "btn_bg2": Color("3a2a1e"), "btn_border": Color("8a6a3a"), "btn_radius": 8,
"text": Color("f0e6d0"), "muted": Color("a89878"), "gold": Color("f0c860"),
}, true)

View File

@@ -0,0 +1 @@
uid://yk4x326b7yvc

BIN
preview/out/1-comic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

BIN
preview/out/1-glas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
preview/out/1-metall.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
preview/out/1-stein.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

BIN
preview/out/2-stein.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

BIN
preview/out/3-arena.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

BIN
preview/out/3-schrein.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 KiB

BIN
preview/out/3-taverne.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 KiB

12
preview/project.godot Normal file
View File

@@ -0,0 +1,12 @@
; Engine configuration file.
config_version=5
[application]
config/name="Preview"
config/features=PackedStringArray("4.7")
[display]
window/size/viewport_width=1280
window/size/viewport_height=800

29
preview/render_svg.gd Normal file
View File

@@ -0,0 +1,29 @@
extends SceneTree
## SVG zu PNG über Godots eigenen Importer (ThorVG). Zeigt, was Godot wirklich darstellt.
## godot --headless --path preview -s render_svg.gd -- in.svg out.png [scale]
func _init() -> void:
var args := OS.get_cmdline_user_args()
if args.size() < 2:
printerr("Aufruf: -- in.svg out.png [scale]")
quit(1)
return
var scale := float(args[2]) if args.size() > 2 else 1.0
var text := FileAccess.get_file_as_string(args[0])
if text == "":
printerr("Kann nicht lesen: " + args[0])
quit(1)
return
var img := Image.new()
var err := img.load_svg_from_string(text, scale)
if err != OK:
printerr("SVG-Fehler %d" % err)
quit(1)
return
err = img.save_png(args[1])
if err != OK:
printerr("PNG-Fehler %d" % err)
quit(1)
return
print("%s %dx%d" % [args[1], img.get_width(), img.get_height()])
quit(0)

View File

@@ -0,0 +1 @@
uid://44y3buahaqat

264
preview/scene_arena.gd Normal file
View File

@@ -0,0 +1,264 @@
extends "res://scene_base.gd"
## Szene Arena: Steinboden bei Nacht, runde Kampfarena, Mondlicht von oben, Fackeln an den Seiten.
const STONE := Color("4a5260")
const STONE_DARK := Color("262c36")
const IRON := Color("8a94a4")
const INK := Color("e8ecf4")
const MUTED := Color("98a2b4")
const BANNER_OPP := Color("7a2630")
const BANNER_OWN := Color("24467a")
const C := Vector2(640, 400)
func build_env() -> void:
light_dir = Vector2(0, 8)
layer(linear_tex(STONE_DARK, darker(STONE_DARK, 0.6)), Rect2(Vector2.ZERO, size))
layer(noise_tex(320, 200, "cellfill", 0.06, 11), Rect2(Vector2.ZERO, size), Color(STONE, 0.35)) # Platten
layer(noise_tex(640, 400, "cell", 0.05, 11), Rect2(Vector2.ZERO, size), Color(0, 0, 0, 0.5)) # Fugen
layer(noise_tex(256, 256, "simplex", 0.08, 4), Rect2(Vector2.ZERO, size), Color(1, 1, 1, 0.06))
light(Vector2(640, 120), 820, Color("b8d0ff"), 0.30) # Mond
light(Vector2(30, 420), 300, Color("ff9a40"), 0.45) # Fackeln
light(Vector2(1250, 420), 300, Color("ff9a40"), 0.45)
for e in ["Feuer", "Wasser", "Erde", "Luft"]: # Runenlicht
light(rune_pos(e), 90, ELEM[e], 0.6)
vignette_layer(0.75)
func rune_pos(e: String) -> Vector2:
match e:
"Feuer": return C + Vector2(-258, 0)
"Erde": return C + Vector2(-190, 0)
"Wasser": return C + Vector2(258, 0)
return C + Vector2(190, 0)
func _draw() -> void:
_arena()
_opponent()
_boards()
_player()
_preview()
txt("Dinger · Szene: Arena", 12, MUTED, Vector2(20, 782), "midleft")
# --- Arena -------------------------------------------------------------------------------
func _arena() -> void:
circle(C, 340, Color(0, 0, 0, 0.35))
circle(C, 330, Color(STONE, 0.55))
ring(C, 330, darker(STONE, 0.5), 10)
ring(C, 335, Color(1, 1, 1, 0.08), 1)
ring(C, 325, Color(0, 0, 0, 0.35), 2)
ring(C, 300, Color(0, 0, 0, 0.25), 3)
ring(C, 301, Color(1, 1, 1, 0.05), 1)
# Mittellinie und Kernstein
draw_line(C + Vector2(-300, 0), C + Vector2(-70, 0), Color(0, 0, 0, 0.45), 3, true)
draw_line(C + Vector2(70, 0), C + Vector2(300, 0), Color(0, 0, 0, 0.45), 3, true)
draw_line(C + Vector2(-300, 1), C + Vector2(-70, 1), Color(1, 1, 1, 0.08), 1, true)
draw_line(C + Vector2(70, 1), C + Vector2(300, 1), Color(1, 1, 1, 0.08), 1, true)
circle(C, 62, Color(0, 0, 0, 0.35))
circle(C, 54, darker(STONE, 0.8))
ring(C, 54, darker(STONE, 0.45), 4)
ring(C, 44, Color(IRON, 0.5), 2)
var pts := PackedVector2Array()
for i in 6:
var a := -PI / 2 + i * PI / 3
pts.append(C + Vector2(cos(a), sin(a)) * 30)
draw_colored_polygon(pts, Color("1a2030"))
for i in 6:
draw_line(C, pts[i], Color(IRON, 0.5), 1, true)
for e in ["Feuer", "Wasser", "Erde", "Luft"]:
var p := rune_pos(e)
circle(p, 22, Color("12161e"))
ring(p, 22, darker(STONE, 0.6), 3)
glyph(e, p, 24, lighter(ELEM[e], 0.35))
# Steinsims für die Hand
var ledge := Rect2(300, 652, 680, 148)
rr(ledge, 6, Color("1c2129"))
border(ledge, 6, Color(0, 0, 0, 0.6), 3)
draw_line(ledge.position + Vector2(4, 2), Vector2(ledge.end.x - 4, ledge.position.y + 2), Color(1, 1, 1, 0.12), 2, true)
func slot(rect: Rect2) -> void:
rr(rect, 4, Color(0, 0, 0, 0.3))
inset(rect, 4, 0.8)
border(rect, 4, Color(1, 1, 1, 0.07), 1)
border(rect.grow(-5), 3, Color(0, 0, 0, 0.25), 1)
# --- Karten als Steintafeln ------------------------------------------------------------------
func card(rect: Rect2, name: String, elem: String, cost: int, atk: int, hp: int, big := false, rare_col = null) -> void:
var base: Color = ELEM[elem]
var rad := 6 if big else 5
if rare_col != null:
glow(rect, rad, rare_col, 9, 0.7)
shadow(rect, rad, 12 if big else 8, 0.6)
grad(rect, rad, lighter(STONE, 0.12), darker(STONE, 0.6))
border(rect, rad, Color(0, 0, 0, 0.7), 2)
draw_line(rect.position + Vector2(4, 2.5), Vector2(rect.end.x - 4, rect.position.y + 2.5), Color(1, 1, 1, 0.2), 1.5, true) # Lichtkante
var inner := rect.grow(-6)
# Fenster in Elementfarbe (Glas)
var pic := Rect2(inner.position, Vector2(inner.size.x, inner.size.y * (0.42 if big else 0.52)))
rr(pic, 4, darker(base, 0.5))
grad(pic.grow(-2), 3, lighter(base, 0.1), darker(base, 0.45))
inset(pic, 4, 1.0)
var gc := pic.get_center()
var gs := pic.size.x * (0.55 if big else 0.6)
circle(gc, gs * 0.45, Color(lighter(base, 0.5), 0.3))
glyph(elem, gc + Vector2(0, 3), gs, Color(0, 0, 0, 0.4))
glyph(elem, gc, gs, lighter(base, 0.5))
# Gemeißeltes Namensfeld
var fs := 14 if big else 11
var band := Rect2(inner.position.x, pic.end.y + 4, inner.size.x, fs + 10)
rr(band, 3, Color("161a22"))
inset(band, 3, 0.9)
txt(fit(name, fs, band.size.x - 12), fs, INK if rare_col == null else lighter(rare_col, 0.3), band.get_center(), "center", true)
if big:
var y := band.end.y + 14
keyword_icon(Vector2(inner.position.x + 24, y), 9, KW[elem], lighter(base, 0.45), Color("161a22"))
txt(KW[elem], 12, INK, Vector2(inner.position.x + 40, y), "midleft")
var box := Rect2(inner.position.x + 4, y + 14, inner.size.x - 8, rect.end.y - 52 - (y + 14))
rr(box, 3, Color("161a22"))
inset(box, 3, 0.7)
txt("Greift zweimal je Zug", 11, INK, box.get_center())
if rare_col != null:
border(rect.grow(-3), rad - 1, Color(rare_col, 0.8), 1)
var r := 15.0 if big else 11.0
var fsz := 17 if big else 13
var m := r + 8
badge(rect.position + Vector2(m, rect.size.y - m), r, str(atk), Color("d89a20"), fsz, IRON)
badge(rect.position + Vector2(rect.size.x - m, rect.size.y - m), r, str(hp), Color("3a78d8"), fsz, IRON)
crystal(rect.position + Vector2(m, m), r + 1, str(cost), fsz, Color("3a6ad0"), IRON)
func card_back(rect: Rect2) -> void:
shadow(rect, 5, 8, 0.6)
grad(rect, 5, lighter(STONE, 0.08), darker(STONE, 0.55))
border(rect, 5, Color(0, 0, 0, 0.7), 2)
var inner := rect.grow(-8)
rr(inner, 3, Color("1a2030"))
inset(inner, 3, 0.9)
var c := rect.get_center()
ring(c, 22, Color(IRON, 0.8), 2)
var pts := PackedVector2Array()
for i in 6:
var a := -PI / 2 + i * PI / 3
pts.append(c + Vector2(cos(a), sin(a)) * 14)
draw_colored_polygon(pts, Color(IRON, 0.5))
ring(c, 6, Color("1a2030"), 2)
func pedestal(rect: Rect2, count: String, label := "") -> void:
var base := Rect2(rect.position.x - 12, rect.end.y - 10, rect.size.x + 24, 26)
shadow(base, 3, 8, 0.6)
grad(base, 3, lighter(STONE, 0.05), darker(STONE, 0.5), 6)
border(base, 3, Color(0, 0, 0, 0.7), 2)
for i in range(5, 0, -1):
var r := Rect2(rect.position + Vector2(0, -i * 2.2), rect.size)
rr(r, 5, Color("2a3140"))
border(r, 5, Color(0, 0, 0, 0.5), 1)
card_back(Rect2(rect.position + Vector2(0, -12), rect.size))
if label != "":
rr(Rect2(rect.position.x + 12, rect.end.y - 64, rect.size.x - 24, 44), 4, Color("12161e"))
txt_shadow(label, 15, INK, Vector2(rect.get_center().x, rect.end.y - 52), "center", true)
txt("◆ 0", 14, Color("8ab8ff"), Vector2(rect.get_center().x, rect.end.y - 32))
txt_shadow(count, 13, INK, Vector2(rect.get_center().x, base.get_center().y + 1), "center", true)
# --- Banner, Runensteine ------------------------------------------------------------------------
func banner(pos: Vector2, col: Color, name: String, life: String, elem: String, mana_full: int, active := false) -> void:
var w := 150.0
var h := 170.0
var pts := PackedVector2Array([pos, pos + Vector2(w, 0), pos + Vector2(w, h - 28), pos + Vector2(w / 2, h), pos + Vector2(0, h - 28)])
var sh := PackedVector2Array()
for p in pts:
sh.append(p + Vector2(0, 8))
draw_colored_polygon(sh, Color(0, 0, 0, 0.45))
draw_colored_polygon(pts, darker(col, 0.8))
var in_pts := PackedVector2Array([pos + Vector2(8, 8), pos + Vector2(w - 8, 8), pos + Vector2(w - 8, h - 32), pos + Vector2(w / 2, h - 12), pos + Vector2(8, h - 32)])
draw_colored_polygon(in_pts, col)
var closed := pts.duplicate()
closed.append(pts[0])
draw_polyline(closed, Color(0, 0, 0, 0.6), 2, true)
# Stange
rr(Rect2(pos.x - 10, pos.y - 8, w + 20, 10), 3, darker(IRON, 0.6))
draw_line(Vector2(pos.x - 8, pos.y - 5), Vector2(pos.x + w + 8, pos.y - 5), lighter(IRON, 0.15), 2, true)
# Emblem
var c := pos + Vector2(w / 2, 58)
circle(c, 34, Color(0, 0, 0, 0.35))
circle(c, 30, darker(col, 0.5))
glyph(elem, c + Vector2(0, 1), 36, lighter(ELEM[elem], 0.4), Color(0, 0, 0, 0.5))
metal_ring(c, 30, 3, IRON if not active else lighter(IRON, 0.2))
txt_shadow(name, 15, INK, pos + Vector2(w / 2, 108), "center", true)
badge(pos + Vector2(w / 2 - 34, 134), 15, life, Color("c83030"), 15, IRON)
for i in 3:
var p := pos + Vector2(w / 2 + 6 + i * 20, 134)
circle(p, 7, Color("3a6ad0") if i < mana_full else Color("1a2030"))
ring(p, 7, darker(IRON, 0.7), 1.5)
func rune_stone(rect: Rect2, elem: String, filled: int, played: String) -> void:
var col: Color = ELEM[elem]
shadow(rect, 10, 6, 0.6)
grad(rect, 10, lighter(STONE, 0.05), darker(STONE, 0.5), 8)
border(rect, 10, Color(0, 0, 0, 0.7), 2)
draw_line(rect.position + Vector2(10, 2.5), Vector2(rect.end.x - 10, rect.position.y + 2.5), Color(1, 1, 1, 0.15), 1.5, true)
circle(Vector2(rect.position.x + 20, rect.get_center().y), 13, Color(col, 0.35))
glyph(elem, Vector2(rect.position.x + 20, rect.get_center().y), 18, lighter(col, 0.4))
txt(elem, 13, INK, Vector2(rect.position.x + 40, rect.get_center().y), "midleft", true)
for k in 2:
keyword_icon(Vector2(rect.position.x + 118 + k * 24, rect.get_center().y), 8, KW[elem], lighter(col, 0.4) if k < filled else Color(col, 0.3), Color("161a22"))
txt(played, 15, lighter(col, 0.4), Vector2(rect.end.x - 12, rect.get_center().y), "midright", true)
func button(rect: Rect2, label: String) -> void:
shadow(rect, 4, 5, 0.5)
grad(rect, 4, lighter(STONE, 0.1), darker(STONE, 0.55), 8)
border(rect, 4, Color(0, 0, 0, 0.7), 2)
draw_line(rect.position + Vector2(4, 2.5), Vector2(rect.end.x - 4, rect.position.y + 2.5), Color(1, 1, 1, 0.2), 1.5, true)
txt_shadow(label, 13, INK, rect.get_center(), "center", true)
# --- Anordnung ---------------------------------------------------------------------------------
func _opponent() -> void:
banner(Vector2(50, 20), BANNER_OPP, "Gegner", "35", "Feuer", 0)
rune_stone(Rect2(30, 220, 210, 36), "Feuer", 1, "1")
button(Rect2(30, 268, 210, 30), "Überspringen")
for i in 4:
var r := Rect2(426 + i * 114, 12, 100, 128)
card_back(r)
pedestal(Rect2(1090, 40, 110, 150), "35")
func _boards() -> void:
for j in 5:
var r := Rect2(342 + j * 122, 172, 110, 140)
if j == 0:
card(r, "Schwelender Drache", "Feuer", 1, 0, 5)
else:
slot(r)
for j in 5:
var r := Rect2(342 + j * 122, 488, 110, 140)
if j == 0:
card(r, "Rieselnder Geist", "Wasser", 1, 1, 2)
else:
slot(r)
func _player() -> void:
banner(Vector2(50, 560), BANNER_OWN, "Spieler", "30", "Wasser", 1, true)
rune_stone(Rect2(30, 500, 210, 36), "Wasser", 1, "1")
var hand := [["Leiser Hauch", "Luft", 0, 0, 1, null], ["Glimmender Wolf", "Feuer", 1, 1, 3, Color("d0d8e4")],
["Stiller Tropfen", "Wasser", 0, 0, 1, null], ["Fester Kiesel", "Erde", 1, 1, 1, Color("f0b030")], ["Rieselnder Geist", "Wasser", 1, 1, 2, null]]
for i in hand.size():
var h = hand[i]
card(Rect2(350 + i * 118, 650, 110, 140), h[0], h[1], h[2], h[3], h[4], false, h[5])
pedestal(Rect2(1090, 610, 110, 150), "35", "Ziehen")
func _preview() -> void:
card(Rect2(1062, 260, 176, 246), "Glimmender Wolf", "Feuer", 1, 1, 3, true, Color("d0d8e4"))

307
preview/scene_base.gd Normal file
View File

@@ -0,0 +1,307 @@
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)

261
preview/scene_schrein.gd Normal file
View File

@@ -0,0 +1,261 @@
extends "res://scene_base.gd"
## Szene Schrein: schwebende Steinplattformen im Nebel, Leuchtkanten, Kristall in der Mitte.
## Licht kommt aus den Dingen selbst (Glühen), dazu kaltes Streiflicht von oben rechts.
const VOID := Color("0c0a16")
const NEBULA := Color("3a2a6a")
const SLAB := Color("2a2438")
const SLAB_DARK := Color("161222")
const TEAL := Color("4ad8d0")
const INK := Color("eef0ff")
const MUTED := Color("9a98c0")
const PLAT_OPP := Rect2(300, 150, 700, 190)
const PLAT_OWN := Rect2(280, 430, 700, 190)
const PLAT_HAND := Rect2(360, 650, 560, 140)
func build_env() -> void:
light_dir = Vector2(0, 12)
layer(linear_tex(darker(NEBULA, 0.5), VOID), Rect2(Vector2.ZERO, size))
layer(noise_tex(320, 200, "simplex", 0.02, 21), Rect2(Vector2.ZERO, size), Color(NEBULA, 0.6))
layer(noise_tex(640, 400, "simplex", 0.12, 22), Rect2(Vector2.ZERO, size), Color("204050"), true)
layer(noise_tex(640, 400, "simplex", 0.12, 22), Rect2(Vector2.ZERO, size), Color(0, 0, 0, 0.45))
# Sterne
var img := Image.create(640, 400, false, Image.FORMAT_RGBA8)
img.fill(Color(0, 0, 0, 0))
var rng := RandomNumberGenerator.new()
rng.seed = 9
for i in 220:
var p := Vector2i(rng.randi_range(0, 639), rng.randi_range(0, 399))
img.set_pixelv(p, Color(1, 1, 1, rng.randf_range(0.2, 0.9)))
layer(ImageTexture.create_from_image(img), Rect2(Vector2.ZERO, size), Color(0.8, 0.85, 1.0, 0.5))
# Glühen unter den Plattformen und um den Kristall
for r in [PLAT_OPP, PLAT_OWN, PLAT_HAND]:
light(Vector2(r.get_center().x, r.end.y + 10), r.size.x * 0.55, TEAL, 0.18)
light(Vector2(640, 392), 180, Color("a0e0ff"), 0.45)
light(Vector2(1100, 60), 500, Color("8090ff"), 0.18)
vignette_layer(0.6)
func _draw() -> void:
_platforms()
_opponent()
_boards()
_player()
_preview()
txt("Dinger · Szene: Schrein", 12, MUTED, Vector2(20, 782), "midleft")
# --- Plattformen ------------------------------------------------------------------------------
func platform(rect: Rect2, edge: Color) -> void:
# Unterseite als dickere Platte mit Bruchkante
var under := Rect2(rect.position + Vector2(6, rect.size.y - 10), Vector2(rect.size.x - 12, 26))
draw_colored_polygon(PackedVector2Array([under.position, Vector2(under.end.x, under.position.y), Vector2(under.end.x - 14, under.end.y), Vector2(under.position.x + 20, under.end.y - 6)]), SLAB_DARK)
shadow(rect, 8, 18, 0.7, Vector2(0, 16))
grad(rect, 8, lighter(SLAB, 0.06), darker(SLAB, 0.7))
border(rect, 8, Color(0, 0, 0, 0.6), 2)
# Leuchtkante oben
draw_line(rect.position + Vector2(10, 2), Vector2(rect.end.x - 10, rect.position.y + 2), Color(edge, 0.9), 2, true)
draw_line(rect.position + Vector2(10, 4), Vector2(rect.end.x - 10, rect.position.y + 4), Color(edge, 0.25), 4, true)
# Runenlinie
for i in range(int(rect.size.x / 46)):
var x := rect.position.x + 24 + i * 46
draw_line(Vector2(x, rect.end.y - 7), Vector2(x + 18, rect.end.y - 7), Color(edge, 0.35), 1.5, true)
func _platforms() -> void:
platform(PLAT_OPP, Color("d06060"))
platform(PLAT_OWN, TEAL)
platform(PLAT_HAND, TEAL)
# Leylinie und Kristall
var c := Vector2(640, 392)
draw_line(Vector2(640, 340), Vector2(640, 430), Color(TEAL, 0.35), 4, true)
draw_line(Vector2(640, 340), Vector2(640, 430), Color("c0ffff"), 1.5, true)
var pts := PackedVector2Array([c + Vector2(0, -44), c + Vector2(22, -12), c + Vector2(16, 30), c + Vector2(0, 46), c + Vector2(-16, 30), c + Vector2(-22, -12)])
draw_colored_polygon(pts, Color("a8e8ff"))
draw_colored_polygon(PackedVector2Array([c + Vector2(0, -44), c + Vector2(22, -12), c + Vector2(0, 0)]), Color(1, 1, 1, 0.5))
draw_colored_polygon(PackedVector2Array([c + Vector2(0, 0), c + Vector2(16, 30), c + Vector2(0, 46), c + Vector2(-16, 30)]), Color("4090c0"))
var closed := pts.duplicate()
closed.append(pts[0])
draw_polyline(closed, Color("e0ffff"), 1.5, true)
var i := 0
for e in ["Feuer", "Luft", "Wasser", "Erde"]:
var a := -PI / 2 + i * PI / 2
var p := c + Vector2(cos(a), sin(a)) * 74
circle(p, 12, Color(ELEM[e], 0.35))
circle(p, 7, lighter(ELEM[e], 0.3))
circle(p + Vector2(-2, -2), 2.5, Color(1, 1, 1, 0.7))
i += 1
func slot(rect: Rect2) -> void:
rr(rect, 6, Color(0, 0, 0, 0.35))
border(rect, 6, Color(TEAL, 0.25), 1)
for k in 4:
var p := rect.position + Vector2(10 + (k % 2) * (rect.size.x - 20), 10 + (k / 2) * (rect.size.y - 20))
circle(p, 2, Color(TEAL, 0.5))
# --- Karten aus Obsidian mit Leuchtrand --------------------------------------------------------------
func card(rect: Rect2, name: String, elem: String, cost: int, atk: int, hp: int, big := false, rare_col = null) -> void:
var base: Color = ELEM[elem]
var edge: Color = rare_col if rare_col != null else lighter(base, 0.25)
var rad := 10 if big else 8
glow(rect, rad, edge, 12 if big else 8, 0.6)
shadow(rect, rad, 14, 0.6, Vector2(0, 10))
grad(rect, rad, lighter(SLAB, 0.04), darker(SLAB, 0.55))
border(rect, rad, Color(edge, 0.9), 2)
border(rect.grow(-2), rad - 2, Color(0, 0, 0, 0.5), 1)
var inner := rect.grow(-6)
var pic := Rect2(inner.position, Vector2(inner.size.x, inner.size.y * (0.42 if big else 0.52)))
rr(pic, rad - 4, darker(base, 0.25))
inset(pic, rad - 4, 0.8)
var gc := pic.get_center()
var gs := pic.size.x * (0.55 if big else 0.6)
for k in 3:
circle(gc, gs * (0.55 - k * 0.12), Color(lighter(base, 0.5), 0.12))
glyph(elem, gc, gs, lighter(base, 0.55))
glyph(elem, gc, gs * 0.6, Color(1, 1, 1, 0.35))
var fs := 14 if big else 11
var band := Rect2(inner.position.x, pic.end.y + 4, inner.size.x, fs + 10)
rr(band, 3, Color(0, 0, 0, 0.45))
draw_line(band.position + Vector2(6, band.size.y - 1), Vector2(band.end.x - 6, band.end.y - 1), Color(edge, 0.8), 1.5, true)
txt(fit(name, fs, band.size.x - 12), fs, INK, band.get_center(), "center", true)
if big:
var y := band.end.y + 14
keyword_icon(Vector2(inner.position.x + 24, y), 9, KW[elem], lighter(base, 0.45), Color(0, 0, 0, 0.6))
txt(KW[elem], 12, INK, Vector2(inner.position.x + 40, y), "midleft")
var box := Rect2(inner.position.x + 4, y + 14, inner.size.x - 8, rect.end.y - 52 - (y + 14))
rr(box, 4, Color(0, 0, 0, 0.45))
txt("Greift zweimal je Zug", 11, INK, box.get_center())
var r := 15.0 if big else 11.0
var fsz := 17 if big else 13
var m := r + 8
badge(rect.position + Vector2(m, rect.size.y - m), r, str(atk), Color("e0a020"), fsz, Color("6a6a80"))
badge(rect.position + Vector2(rect.size.x - m, rect.size.y - m), r, str(hp), Color("3a78d8"), fsz, Color("6a6a80"))
crystal(rect.position + Vector2(m, m), r + 1, str(cost), fsz, Color("3a6ad0"), Color("9ad8ff"))
func card_back(rect: Rect2) -> void:
glow(rect, 8, Color("8060d0"), 8, 0.5)
shadow(rect, 8, 10, 0.6, Vector2(0, 8))
grad(rect, 8, lighter(SLAB, 0.04), darker(SLAB, 0.55))
border(rect, 8, Color("a080ff"), 2)
var c := rect.get_center()
for k in 3:
circle(c, 30 - k * 8, Color("8060d0", 0.18))
var pts := PackedVector2Array([c + Vector2(0, -22), c + Vector2(11, -6), c + Vector2(8, 15), c + Vector2(0, 23), c + Vector2(-8, 15), c + Vector2(-11, -6)])
draw_colored_polygon(pts, Color("c0a8ff"))
draw_colored_polygon(PackedVector2Array([c + Vector2(0, -22), c + Vector2(11, -6), c]), Color(1, 1, 1, 0.4))
var closed := pts.duplicate()
closed.append(pts[0])
draw_polyline(closed, Color("e8e0ff"), 1.2, true)
func obelisk(rect: Rect2, count: String, label := "") -> void:
for i in range(5, 0, -1):
var r := Rect2(rect.position + Vector2(0, -i * 2.2), rect.size)
rr(r, 8, Color("1c1828"))
border(r, 8, Color("5040a0", 0.5), 1)
card_back(Rect2(rect.position + Vector2(0, -12), rect.size))
if label != "":
rr(Rect2(rect.position.x + 12, rect.end.y - 76, rect.size.x - 24, 44), 4, Color(0, 0, 0, 0.55))
txt_shadow(label, 15, INK, Vector2(rect.get_center().x, rect.end.y - 64), "center", true)
txt("◆ 0", 14, Color("8ab8ff"), Vector2(rect.get_center().x, rect.end.y - 44))
pill(Rect2(rect.position.x + 30, rect.end.y - 4, rect.size.x - 60, 22), count, Color("a080ff"))
func pill(rect: Rect2, label: String, edge: Color, fs := 13) -> void:
rr(rect, int(rect.size.y / 2), Color(0, 0, 0, 0.55))
border(rect, int(rect.size.y / 2), Color(edge, 0.7), 1)
txt(label, fs, INK, rect.get_center(), "center", true)
func orb(c: Vector2, name: String, life: String, elem: String, mana_full: int, active := false) -> void:
var col: Color = ELEM[elem]
for k in 4:
circle(c, 56 + k * 8, Color(col, 0.07))
circle(c, 44, darker(SLAB, 0.8))
circle(c, 42, Color(col, 0.45))
for k in 3:
circle(c, 40 - k * 10, Color(lighter(col, 0.3), 0.12))
glyph(elem, c + Vector2(0, 2), 46, lighter(col, 0.5), Color(0, 0, 0, 0.4))
circle(c + Vector2(-14, -16), 10, Color(1, 1, 1, 0.22))
ring(c, 44, lighter(col, 0.35) if active else Color(col, 0.6), 3)
if active:
ring(c, 50, Color(TEAL, 0.7), 1.5)
pill(Rect2(c.x - 60, c.y + 52, 120, 24), name, lighter(col, 0.3), 14)
badge(c + Vector2(-40, 34), 16, life, Color("c83030"), 15, Color("6a6a80"))
for i in 3:
var p := Vector2(c.x - 20 + i * 20, c.y + 92)
circle(p, 6, Color("3a6ad0") if i < mana_full else Color("1a1a30"))
ring(p, 6, Color("9ad8ff", 0.8 if i < mana_full else 0.3), 1.5)
func rune_pill(rect: Rect2, elem: String, filled: int, played: String) -> void:
var col: Color = ELEM[elem]
pill(rect, "", lighter(col, 0.2))
circle(Vector2(rect.position.x + 18, rect.get_center().y), 11, Color(col, 0.35))
glyph(elem, Vector2(rect.position.x + 18, rect.get_center().y), 16, lighter(col, 0.45))
txt(elem, 13, INK, Vector2(rect.position.x + 36, rect.get_center().y), "midleft", true)
for k in 2:
keyword_icon(Vector2(rect.position.x + 114 + k * 24, rect.get_center().y), 8, KW[elem], lighter(col, 0.4) if k < filled else Color(col, 0.3), Color(0, 0, 0, 0.6))
txt(played, 15, lighter(col, 0.4), Vector2(rect.end.x - 14, rect.get_center().y), "midright", true)
func button(rect: Rect2, label: String) -> void:
glow(rect, 6, TEAL, 5, 0.3)
grad(rect, 6, lighter(SLAB, 0.1), darker(SLAB, 0.6), 8)
border(rect, 6, Color(TEAL, 0.7), 1)
txt_shadow(label, 13, INK, rect.get_center(), "center", true)
# --- Anordnung ---------------------------------------------------------------------------------------
func _opponent() -> void:
orb(Vector2(130, 110), "Gegner", "35", "Feuer", 0)
rune_pill(Rect2(30, 232, 210, 32), "Feuer", 1, "1")
button(Rect2(30, 276, 210, 30), "Überspringen")
for i in 4:
var t := i - 1.5
var r := Rect2(430 + i * 112, 10, 100, 128)
draw_set_transform(r.get_center() + Vector2(0, t * t * 2), deg_to_rad(-t * 2))
card_back(Rect2(-r.size / 2, r.size))
draw_set_transform(Vector2.ZERO)
obelisk(Rect2(1090, 40, 110, 150), "35")
func _boards() -> void:
for j in 5:
var r := Rect2(352 + j * 122, 172, 110, 140)
if j == 0:
card(r, "Schwelender Drache", "Feuer", 1, 0, 5)
else:
slot(r)
for j in 5:
var r := Rect2(332 + j * 122, 452, 110, 140)
if j == 0:
card(r, "Rieselnder Geist", "Wasser", 1, 1, 2)
else:
slot(r)
func _player() -> void:
orb(Vector2(130, 600), "Spieler", "30", "Wasser", 1, true)
rune_pill(Rect2(30, 476, 210, 32), "Wasser", 1, "1")
var hand := [["Leiser Hauch", "Luft", 0, 0, 1, null], ["Glimmender Wolf", "Feuer", 1, 1, 3, Color("d0d8e4")],
["Stiller Tropfen", "Wasser", 0, 0, 1, null], ["Fester Kiesel", "Erde", 1, 1, 1, Color("f0b030")], ["Rieselnder Geist", "Wasser", 1, 1, 2, null]]
for i in hand.size():
var h = hand[i]
var t := i - 2.0
var c := Vector2(640 + t * 104, 700 + t * t * 2)
draw_set_transform(c, deg_to_rad(t * 4))
card(Rect2(-55, -70, 110, 140), h[0], h[1], h[2], h[3], h[4], false, h[5])
draw_set_transform(Vector2.ZERO)
obelisk(Rect2(1090, 616, 110, 150), "35", "Ziehen")
func _preview() -> void:
card(Rect2(1062, 250, 176, 246), "Glimmender Wolf", "Feuer", 1, 1, 3, true, Color("d0d8e4"))

251
preview/scene_taverne.gd Normal file
View File

@@ -0,0 +1,251 @@
extends "res://scene_base.gd"
## Szene Taverne: Holztisch, Filz-Spielmatte, Messing, warmes Lampenlicht von oben links.
const WOOD := Color("5a3a22")
const WOOD_LIGHT := Color("8a5a34")
const FELT := Color("3c5e46")
const BRASS := Color("c9a046")
const DARK := Color("1a120c")
const INK := Color("f0e2c0")
const MUTED := Color("b8a888")
const MAT := Rect2(250, 60, 780, 690)
func build_env() -> void:
light_dir = Vector2(4, 7)
# Tisch: Holz mit waagerechter Maserung, zwei Lagen
layer(linear_tex(WOOD, darker(WOOD, 0.7)), Rect2(Vector2.ZERO, size))
layer(noise_tex(48, 800, "simplex", 0.08, 3), Rect2(Vector2.ZERO, size), Color(WOOD_LIGHT, 0.55))
layer(noise_tex(256, 256, "simplex", 0.05, 7), Rect2(Vector2.ZERO, size), Color(0, 0, 0, 0.25))
# Spielmatte aus Filz mit leichter Struktur
layer(linear_tex(FELT, darker(FELT, 0.75)), MAT.grow(-6))
layer(noise_tex(390, 345, "simplex", 0.9, 5), MAT.grow(-6), Color(1, 1, 1, 0.07))
# Lampenlicht von oben, Kerze beim eigenen Stapel
light(Vector2(560, 160), 760, Color("ffd9a0"), 0.42)
light(Vector2(640, 420), 520, Color("ffe8c0"), 0.22)
light(Vector2(1150, 720), 220, Color("ffb060"), 0.35)
vignette_layer(0.7)
func _draw() -> void:
_mat()
_opponent()
_boards()
_player()
_preview()
txt("Dinger · Szene: Taverne", 12, MUTED, Vector2(20, 782), "midleft")
# --- Matte und Zonen ------------------------------------------------------------------
func _mat() -> void:
shadow(MAT, 14, 18, 0.6)
border(MAT, 14, darker(WOOD, 0.5), 6) # Lederkante
border(MAT.grow(-3), 12, Color("3a2816"), 2)
engrave(MAT.grow(-14), 10, Color(0, 0, 0, 0.35), Color(1, 1, 1, 0.06)) # Naht
# Zonen als eingedrückte Felder
for z in [Rect2(330, 170, 620, 160), Rect2(330, 430, 620, 160)]:
rr(z, 10, Color(0, 0, 0, 0.18))
inset(z, 10, 0.6)
# Mittellinie mit Elementkompass
draw_line(Vector2(290, 400), Vector2(560, 400), Color(0, 0, 0, 0.4), 2, true)
draw_line(Vector2(720, 400), Vector2(990, 400), Color(0, 0, 0, 0.4), 2, true)
draw_line(Vector2(290, 401), Vector2(560, 401), Color(1, 1, 1, 0.08), 1, true)
draw_line(Vector2(720, 401), Vector2(990, 401), Color(1, 1, 1, 0.08), 1, true)
var c := Vector2(640, 400)
circle(c, 58, Color(0, 0, 0, 0.25))
ring(c, 58, Color(0, 0, 0, 0.35), 3)
ring(c, 59, Color(1, 1, 1, 0.07), 1)
metal_ring(c, 44, 3, darker(BRASS, 0.8))
var dirs := {"Feuer": Vector2(0, -1), "Wasser": Vector2(0, 1), "Erde": Vector2(-1, 0), "Luft": Vector2(1, 0)}
for e in dirs:
glyph(e, c + dirs[e] * 26, 20, Color(ELEM[e], 0.85))
func slot(rect: Rect2) -> void:
rr(rect, 8, Color(0, 0, 0, 0.22))
inset(rect, 8, 0.7)
border(rect, 8, Color(1, 1, 1, 0.05), 1)
# --- Karten ---------------------------------------------------------------------------
func card(rect: Rect2, name: String, elem: String, cost: int, atk: int, hp: int, big := false, rare_col = null) -> void:
var base: Color = ELEM[elem]
var rad := 10 if big else 8
if rare_col != null:
glow(rect, rad, rare_col, 9, 0.7)
shadow(rect, rad, 10 if big else 7, 0.55)
rr(rect, rad, DARK)
var inner := rect.grow(-3)
grad(inner, rad - 2, lighter(base, 0.08), darker(base, 0.55))
# Bildfeld mit Lichtkern hinter dem Symbol
var pic := Rect2(inner.position + Vector2(5, 5), Vector2(inner.size.x - 10, inner.size.y * (0.40 if big else 0.5)))
rr(pic, rad - 4, Color(0, 0, 0, 0.18))
inset(pic, rad - 4, 0.6)
var gc := pic.get_center()
var gs := pic.size.x * (0.55 if big else 0.6)
circle(gc, gs * 0.42, Color(lighter(base, 0.45), 0.25))
glyph(elem, gc + Vector2(2, 3), gs, Color(0, 0, 0, 0.35))
glyph(elem, gc, gs, lighter(base, 0.42))
# Namensband
var fs := 14 if big else 11
var band := Rect2(inner.position.x - 2, pic.end.y + 3, inner.size.x + 4, fs + 12)
ribbon(band, DARK, BRASS if rare_col == null else rare_col)
txt(fit(name, fs, band.size.x - 24), fs, INK if rare_col == null else lighter(rare_col, 0.3), band.get_center(), "center", true)
if big:
var y := band.end.y + 14
keyword_icon(Vector2(inner.position.x + 30, y), 9, KW[elem], lighter(base, 0.45))
txt(KW[elem], 12, INK, Vector2(inner.position.x + 46, y), "midleft")
var box := Rect2(inner.position.x + 8, y + 14, inner.size.x - 16, rect.end.y - 52 - (y + 14))
rr(box, 6, Color(0.05, 0.04, 0.03, 0.55))
txt("Greift zweimal je Zug", 11, INK, box.get_center())
border(inner, rad - 2, Color(BRASS if rare_col == null else rare_col, 0.9), 1)
var r := 15.0 if big else 11.0
var fsz := 17 if big else 13
var m := r + 8
badge(rect.position + Vector2(m, rect.size.y - m), r, str(atk), Color("e0a020"), fsz, BRASS)
badge(rect.position + Vector2(rect.size.x - m, rect.size.y - m), r, str(hp), Color("3a78d8"), fsz, BRASS)
crystal(rect.position + Vector2(m, m), r + 1, str(cost), fsz, Color("3a6ad0"), BRASS)
func card_back(rect: Rect2) -> void:
shadow(rect, 8, 7, 0.55)
rr(rect, 8, DARK)
var inner := rect.grow(-3)
grad(inner, 6, Color("6a2a24"), Color("30120e"))
border(inner.grow(-6), 4, Color(BRASS, 0.8), 1)
border(inner.grow(-9), 3, Color(BRASS, 0.35), 1)
var c := rect.get_center()
circle(c, 26, Color(0, 0, 0, 0.3))
metal_ring(c, 24, 2.5, BRASS)
var star := PackedVector2Array()
for i in 8:
var a := -PI / 2 + i * PI / 4
star.append(c + Vector2(cos(a), sin(a)) * (16 if i % 2 == 0 else 7))
draw_colored_polygon(star, BRASS)
circle(c, 4, DARK)
func deck(rect: Rect2, count: String, label := "") -> void:
for i in range(5, 0, -1):
var r := Rect2(rect.position + Vector2(i * 1.5, -i * 2), rect.size)
rr(r, 8, DARK)
rr(r.grow(-3), 6, Color("4a1e18"))
card_back(rect)
if label != "":
rr(Rect2(rect.position.x + 14, rect.end.y - 54, rect.size.x - 28, 40), 6, Color(0, 0, 0, 0.55))
txt_shadow(label, 15, INK, Vector2(rect.get_center().x, rect.end.y - 42), "center", true)
txt("◆ 0", 14, Color("8ab8ff"), Vector2(rect.get_center().x, rect.end.y - 24))
plaque(Rect2(rect.position.x + 36, rect.position.y - 14, rect.size.x - 72, 24), count, 13)
# --- Messing-Plaketten, Helden -------------------------------------------------------------
func plaque(rect: Rect2, label: String, fs := 13, col := INK) -> void:
shadow(rect, 4, 4, 0.5)
grad(rect, 4, lighter(BRASS, 0.08), darker(BRASS, 0.6), 8)
border(rect, 4, darker(BRASS, 0.4), 1)
rr(rect.grow(-4), 2, Color("2a1c10"))
txt(label, fs, col, rect.get_center(), "center", true)
func element_plaque(rect: Rect2, elem: String, filled: int, played: String) -> void:
shadow(rect, 4, 4, 0.5)
grad(rect, 4, lighter(BRASS, 0.08), darker(BRASS, 0.6), 8)
border(rect, 4, darker(BRASS, 0.4), 1)
var in_r := rect.grow(-4)
rr(in_r, 2, Color("2a1c10"))
var col: Color = ELEM[elem]
glyph(elem, Vector2(in_r.position.x + 16, in_r.get_center().y), 18, col)
txt(elem, 13, INK, Vector2(in_r.position.x + 34, in_r.get_center().y), "midleft", true)
for k in 2:
keyword_icon(Vector2(in_r.position.x + 112 + k * 24, in_r.get_center().y), 8, KW[elem], lighter(col, 0.4) if k < filled else Color(col, 0.3))
txt(played, 15, lighter(col, 0.4), Vector2(in_r.end.x - 12, in_r.get_center().y), "midright", true)
## Held: Medaillon mit Messingring, Lebensgem links unten, Name auf Plakette, Mana als Kristalle.
func hero(c: Vector2, name: String, life: String, elem: String, mana_full: int, mana_max: int, active := false) -> void:
circle(c + light_dir, 50, Color(0, 0, 0, 0.5))
circle(c, 48, Color("2a1c10"))
circle(c, 44, Color(ELEM[elem], 0.35))
inset(Rect2(c - Vector2(44, 44), Vector2(88, 88)), 44, 0.8)
glyph(elem, c + Vector2(0, 2), 50, Color(lighter(ELEM[elem], 0.4), 0.9), Color(0, 0, 0, 0.5))
metal_ring(c, 48, 5, lighter(BRASS, 0.05) if active else darker(BRASS, 0.8))
if active:
ring(c, 54, Color("ffd980"), 2)
plaque(Rect2(c.x - 60, c.y + 54, 120, 26), name, 14)
badge(c + Vector2(-42, 36), 17, life, Color("c83030"), 16, BRASS)
# Mana: Kristalle in einer Reihe
for i in mana_max:
var p := Vector2(c.x - 60 + 14 + i * 20, c.y + 100)
var col := Color("3a6ad0") if i < mana_full else Color("1c2434")
var pts := PackedVector2Array()
for k in 6:
var a := -PI / 2 + k * PI / 3
pts.append(p + Vector2(cos(a), sin(a)) * 8)
draw_colored_polygon(pts, col)
var closed := pts.duplicate()
closed.append(pts[0])
draw_polyline(closed, darker(BRASS, 0.7), 1.5, true)
func button(rect: Rect2, label: String) -> void:
shadow(rect, 6, 5, 0.5)
grad(rect, 6, Color("7a5230"), Color("3e2814"), 8)
border(rect, 6, darker(BRASS, 0.6), 2)
border(rect.grow(-3), 4, Color(1, 1, 1, 0.06), 1)
txt_shadow(label, 13, INK, rect.get_center(), "center", true)
# --- Anordnung ------------------------------------------------------------------------------
func _opponent() -> void:
hero(Vector2(130, 110), "Gegner", "35", "Feuer", 0, 3)
element_plaque(Rect2(30, 240, 200, 34), "Feuer", 1, "1")
button(Rect2(30, 290, 200, 30), "Überspringen")
for i in 4:
var t := i - 1.5
var r := Rect2(430 + i * 112, 14, 100, 128)
draw_set_transform(r.get_center() + Vector2(0, t * t * 2), deg_to_rad(-t * 2.5))
card_back(Rect2(-r.size / 2, r.size))
draw_set_transform(Vector2.ZERO)
deck(Rect2(1090, 40, 110, 150), "35")
func _boards() -> void:
for j in 5:
var r := Rect2(340 + j * 122, 180, 110, 140)
if j == 0:
card(r, "Schwelender Drache", "Feuer", 1, 0, 5)
else:
slot(r)
for j in 5:
var r := Rect2(340 + j * 122, 440, 110, 140)
if j == 0:
card(r, "Rieselnder Geist", "Wasser", 1, 1, 2)
else:
slot(r)
func _player() -> void:
hero(Vector2(130, 600), "Spieler", "30", "Wasser", 1, 3, true)
element_plaque(Rect2(30, 470, 200, 34), "Wasser", 1, "1")
# Hand als Fächer in einer Lederrinne
var tray := Rect2(360, 628, 560, 150)
rr(tray, 16, Color(0, 0, 0, 0.3))
inset(tray, 16, 0.8)
var hand := [["Leiser Hauch", "Luft", 0, 0, 1, null], ["Glimmender Wolf", "Feuer", 1, 1, 3, Color("d0d8e4")],
["Stiller Tropfen", "Wasser", 0, 0, 1, null], ["Fester Kiesel", "Erde", 1, 1, 1, Color("f0b030")], ["Rieselnder Geist", "Wasser", 1, 1, 2, null]]
for i in hand.size():
var h = hand[i]
var t := i - 2.0
var c := Vector2(640 + t * 104, 698 + t * t * 3)
draw_set_transform(c, deg_to_rad(t * 5))
card(Rect2(-55, -70, 110, 140), h[0], h[1], h[2], h[3], h[4], false, h[5])
draw_set_transform(Vector2.ZERO)
deck(Rect2(1090, 620, 110, 150), "35", "Ziehen")
func _preview() -> void:
card(Rect2(1062, 250, 176, 246), "Glimmender Wolf", "Feuer", 1, 1, 3, true, Color("d0d8e4"))

34
preview/shot.gd Normal file
View File

@@ -0,0 +1,34 @@
extends SceneTree
## Lädt eine Mockup-Szene oder ein Control-Skript, macht einen Screenshot und beendet.
## godot --path preview -s shot.gd -- mockup.gd out.png
## Braucht ein Fenster (kein --headless). Fenstergröße aus project.godot: 1280×800.
func _init() -> void:
var args := OS.get_cmdline_user_args()
if args.size() < 2:
printerr("Aufruf: -- szene.tscn|skript.gd out.png")
quit(1)
return
var node: Node
if args[0].ends_with(".tscn"):
node = (load(args[0]) as PackedScene).instantiate()
else:
node = (load(args[0]) as GDScript).new()
if node is Control:
node.size = Vector2(root.size) # vor _ready, damit Kinder die Größe kennen
root.add_child(node)
if node is Control:
node.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_shot(args[1])
func _shot(out: String) -> void:
for i in 6:
await process_frame
await RenderingServer.frame_post_draw
var err := root.get_texture().get_image().save_png(out)
if err != OK:
printerr("PNG-Fehler %d" % err)
quit(1)
return
print("shot " + out)
quit(0)

1
preview/shot.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://ceegw54mx8kpl