commit b9ff5c1e64be495a3202e7b307b47248207d3ab4 Author: team3 Date: Sat Oct 10 16:35:28 2026 +0200 init diff --git a/preview/.godot/.gdignore b/preview/.godot/.gdignore new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/preview/.godot/.gdignore @@ -0,0 +1 @@ + diff --git a/preview/.godot/editor/filesystem_cache10 b/preview/.godot/editor/filesystem_cache10 new file mode 100644 index 0000000..a543f7e --- /dev/null +++ b/preview/.godot/editor/filesystem_cache10 @@ -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 diff --git a/preview/.godot/editor/project_metadata.cfg b/preview/.godot/editor/project_metadata.cfg new file mode 100644 index 0000000..8c3b5ac --- /dev/null +++ b/preview/.godot/editor/project_metadata.cfg @@ -0,0 +1,7 @@ +[game_view] + +select_mode=0 + +[editor_metadata] + +executable_path="/home/arbeit/bin/godot" diff --git a/preview/.godot/global_script_class_cache.cfg b/preview/.godot/global_script_class_cache.cfg new file mode 100644 index 0000000..32c2394 --- /dev/null +++ b/preview/.godot/global_script_class_cache.cfg @@ -0,0 +1 @@ +list=[] diff --git a/preview/.godot/uid_cache.bin b/preview/.godot/uid_cache.bin new file mode 100644 index 0000000..2846ba3 Binary files /dev/null and b/preview/.godot/uid_cache.bin differ diff --git a/preview/README.md b/preview/README.md new file mode 100644 index 0000000..e009d6d --- /dev/null +++ b/preview/README.md @@ -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. diff --git a/preview/mockup_base.gd b/preview/mockup_base.gd new file mode 100644 index 0000000..7b34617 --- /dev/null +++ b/preview/mockup_base.gd @@ -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") diff --git a/preview/mockup_base.gd.uid b/preview/mockup_base.gd.uid new file mode 100644 index 0000000..e2553e3 --- /dev/null +++ b/preview/mockup_base.gd.uid @@ -0,0 +1 @@ +uid://kafs6twkfjb2 diff --git a/preview/mockup_comic.gd b/preview/mockup_comic.gd new file mode 100644 index 0000000..2c07a81 --- /dev/null +++ b/preview/mockup_comic.gd @@ -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) diff --git a/preview/mockup_comic.gd.uid b/preview/mockup_comic.gd.uid new file mode 100644 index 0000000..4f079cd --- /dev/null +++ b/preview/mockup_comic.gd.uid @@ -0,0 +1 @@ +uid://b7wahxecyn8t1 diff --git a/preview/mockup_glas.gd b/preview/mockup_glas.gd new file mode 100644 index 0000000..6af0719 --- /dev/null +++ b/preview/mockup_glas.gd @@ -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) diff --git a/preview/mockup_glas.gd.uid b/preview/mockup_glas.gd.uid new file mode 100644 index 0000000..89560d9 --- /dev/null +++ b/preview/mockup_glas.gd.uid @@ -0,0 +1 @@ +uid://dujvwdfy6a637 diff --git a/preview/mockup_metall.gd b/preview/mockup_metall.gd new file mode 100644 index 0000000..3678071 --- /dev/null +++ b/preview/mockup_metall.gd @@ -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) diff --git a/preview/mockup_metall.gd.uid b/preview/mockup_metall.gd.uid new file mode 100644 index 0000000..0852e09 --- /dev/null +++ b/preview/mockup_metall.gd.uid @@ -0,0 +1 @@ +uid://c40gniw2kyopy diff --git a/preview/mockup_stein.gd b/preview/mockup_stein.gd new file mode 100644 index 0000000..259353e --- /dev/null +++ b/preview/mockup_stein.gd @@ -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) diff --git a/preview/mockup_stein.gd.uid b/preview/mockup_stein.gd.uid new file mode 100644 index 0000000..d83b883 --- /dev/null +++ b/preview/mockup_stein.gd.uid @@ -0,0 +1 @@ +uid://yk4x326b7yvc diff --git a/preview/out/1-comic.png b/preview/out/1-comic.png new file mode 100644 index 0000000..843d1c0 Binary files /dev/null and b/preview/out/1-comic.png differ diff --git a/preview/out/1-glas.png b/preview/out/1-glas.png new file mode 100644 index 0000000..3055595 Binary files /dev/null and b/preview/out/1-glas.png differ diff --git a/preview/out/1-metall.png b/preview/out/1-metall.png new file mode 100644 index 0000000..b11ea4c Binary files /dev/null and b/preview/out/1-metall.png differ diff --git a/preview/out/1-stein.png b/preview/out/1-stein.png new file mode 100644 index 0000000..3bac098 Binary files /dev/null and b/preview/out/1-stein.png differ diff --git a/preview/out/2-stein.png b/preview/out/2-stein.png new file mode 100644 index 0000000..078e1df Binary files /dev/null and b/preview/out/2-stein.png differ diff --git a/preview/out/3-arena.png b/preview/out/3-arena.png new file mode 100644 index 0000000..1f3e8ba Binary files /dev/null and b/preview/out/3-arena.png differ diff --git a/preview/out/3-schrein.png b/preview/out/3-schrein.png new file mode 100644 index 0000000..7fdb54f Binary files /dev/null and b/preview/out/3-schrein.png differ diff --git a/preview/out/3-taverne.png b/preview/out/3-taverne.png new file mode 100644 index 0000000..d1c8b08 Binary files /dev/null and b/preview/out/3-taverne.png differ diff --git a/preview/project.godot b/preview/project.godot new file mode 100644 index 0000000..e7fceb0 --- /dev/null +++ b/preview/project.godot @@ -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 diff --git a/preview/render_svg.gd b/preview/render_svg.gd new file mode 100644 index 0000000..afbd050 --- /dev/null +++ b/preview/render_svg.gd @@ -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) diff --git a/preview/render_svg.gd.uid b/preview/render_svg.gd.uid new file mode 100644 index 0000000..4126c09 --- /dev/null +++ b/preview/render_svg.gd.uid @@ -0,0 +1 @@ +uid://44y3buahaqat diff --git a/preview/scene_arena.gd b/preview/scene_arena.gd new file mode 100644 index 0000000..14e797b --- /dev/null +++ b/preview/scene_arena.gd @@ -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")) diff --git a/preview/scene_base.gd b/preview/scene_base.gd new file mode 100644 index 0000000..98fdfde --- /dev/null +++ b/preview/scene_base.gd @@ -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) diff --git a/preview/scene_schrein.gd b/preview/scene_schrein.gd new file mode 100644 index 0000000..194922f --- /dev/null +++ b/preview/scene_schrein.gd @@ -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")) diff --git a/preview/scene_taverne.gd b/preview/scene_taverne.gd new file mode 100644 index 0000000..b73d5be --- /dev/null +++ b/preview/scene_taverne.gd @@ -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")) diff --git a/preview/shot.gd b/preview/shot.gd new file mode 100644 index 0000000..15337fc --- /dev/null +++ b/preview/shot.gd @@ -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) diff --git a/preview/shot.gd.uid b/preview/shot.gd.uid new file mode 100644 index 0000000..5eabff5 --- /dev/null +++ b/preview/shot.gd.uid @@ -0,0 +1 @@ +uid://ceegw54mx8kpl