Files
spacel/scripts/hud_draw.gd
T
alpacaman edc40f9008 Initial commit — Godot space roguelite source
- Touch controls: direct InputEventScreenTouch in shop_ui (bypass relay)
- ItemDB: static preload list instead of DirAccess scan (export fix)
- All 18 items with EN localisation (name_en, desc_en, category_en)
- Ship playstyles: NOVA-1 shield, INFERNO ram, AURORA agile/tank
- Quasar: SMBH visual, jet boost, merge, push, BH-eating
- Atlas & UI text updated EN+DE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 14:38:09 +02:00

233 lines
11 KiB
GDScript

extends Node2D
# Minimal cockpit HUD — maximum screen space for the game.
# During play: only 4 corner L-brackets + tiny top-right status line.
# On gameover: full terminal overlay.
var W: float = 960.0
var H: float = 600.0
# Cockpit palette
const COL_PRIMARY := Color(0.0, 1.0, 0.533, 0.55) # phosphor green, semi-dim
const COL_ACCENT := Color(0.27, 1.0, 0.8, 0.80)
const COL_DIM := Color(0.0, 0.27, 0.13, 0.45)
const COL_WARNING := Color(1.0, 0.67, 0.0, 0.90)
const COL_DANGER := Color(1.0, 0.2, 0.0, 0.90)
const COL_WHITE := Color(1.0, 1.0, 1.0, 0.90)
const BRACKET_LEN := 14.0
const BRACKET_W := 1.5
const MARGIN := 10.0
var hud: Node = null
func _process(_delta: float) -> void:
queue_redraw()
func _draw() -> void:
if hud == null:
return
var vs: Vector2 = get_viewport_rect().size
W = vs.x; H = vs.y
var font := ThemeDB.fallback_font
var gw: Node = hud.game_world
# ── Countdown ────────────────────────────────────────────────────────────
if hud.show_countdown_flag and hud.countdown_value > 0:
_draw_corner_brackets()
var ct := str(hud.countdown_value)
_draw_text_centered(font, ct, W * 0.5, H * 0.5 - 36.0, 64, COL_ACCENT)
_draw_text_centered(font, Tr.t("hud_launching"), W * 0.5, H * 0.5 + 28.0, 10, COL_DIM)
return
if not hud.is_gameover and gw == null:
return
# ── Normal gameplay HUD ──────────────────────────────────────────────────
if not hud.is_gameover and gw != null:
_draw_corner_brackets()
# ── Wave timer bar ────────────────────────────────────────────────
var wave_dur: float = float(gw.get("wave_duration") if gw.get("wave_duration") != null else 60.0)
var wave_t: float = float(gw.get("wave_timer") if gw.get("wave_timer") != null else 0.0)
var wave_frac: float = clamp(wave_t / wave_dur, 0.0, 1.0)
var wave_remaining: float = max(0.0, wave_dur - wave_t)
var last_10: bool = wave_remaining <= 10.0 and gw.get("is_playing")
# Thin bar at top — shrinks from right as time passes
var bar_col: Color
if last_10:
bar_col = Color(COL_WARNING.r, COL_WARNING.g, COL_WARNING.b,
0.55 + 0.45 * sin(hud.blink_phase * 8.0))
else:
bar_col = Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, 0.35)
draw_rect(Rect2(0.0, 0.0, W * (1.0 - wave_frac), 2.0), bar_col)
# Wave label top-left: "W1 0:42"
var w_mins: int = int(wave_remaining / 60.0)
var w_secs: int = int(wave_remaining) % 60
var wave_label := "W%d %d:%02d" % [hud.wave_number, w_mins, w_secs]
var wlabel_col: Color = COL_WARNING if last_10 else COL_DIM
_draw_text(font, wave_label, MARGIN, 10.0, 9, wlabel_col)
# ── Wave cleared overlay ──────────────────────────────────────────
if hud.wave_cleared:
var wc_a: float = 0.7 + 0.3 * sin(hud.blink_phase * 5.0)
_draw_text_centered(font, Tr.t("hud_wave_clear") % (hud.wave_number - 1),
W * 0.5, H * 0.5 - 22.0, 16, Color(COL_ACCENT.r, COL_ACCENT.g, COL_ACCENT.b, wc_a))
_draw_text_centered(font, Tr.t("hud_to_shop"), W * 0.5, H * 0.5 + 8.0, 11,
Color(COL_PRIMARY.r, COL_PRIMARY.g, COL_PRIMARY.b, wc_a * 0.7))
# Top-right micro status — lives dots + credits + time + kills
var players: Array = gw.players
var lives_str := ""
for li in 3:
lives_str += ("" if li < hud.lives else "")
var cr_str := "CR:%d" % hud.credits
if not gw.is_multiplayer:
if players.size() > 0:
var p = players[0]
var mins: int = int(p.survival_time / 60.0)
var secs: int = int(p.survival_time) % 60
var time_str := "%02d:%02d" % [mins, secs]
var shields_str := ""
for _si in p.current_shields:
shields_str += ""
var status_str := "%s %s %s %s x%d" % [lives_str, cr_str, shields_str, time_str, p.kills]
_draw_text(font, status_str, W - 200.0, 10.0, 9, COL_PRIMARY)
else:
if players.size() >= 1:
var p1 = players[0]
var t1 := "%02d:%02d" % [int(p1.survival_time / 60.0), int(p1.survival_time) % 60]
_draw_text(font, "P1 %s x%d %s" % [t1, p1.kills, cr_str], 10.0, 10.0, 9, COL_PRIMARY)
if players.size() >= 2:
var p2 = players[1]
var t2 := "%02d:%02d" % [int(p2.survival_time / 60.0), int(p2.survival_time) % 60]
_draw_text(font, "P2 %s x%d" % [t2, p2.kills], W - 110.0, 10.0, 9,
Color(0.27, 1.0, 0.67, 0.55))
# ── Boost-Cooldown-Leisten (nur Schiffe mit has_boost) ────────────
for player in players:
var sp: Spaceship = player
if sp == null or sp.dead: continue
if sp.stats == null or not sp.stats.has_boost: continue
var ratio: float = sp.boost_ratio()
var bar_w := 40.0; var bar_h := 4.0
var bx2: float = sp.x - bar_w * 0.5
var by2: float = sp.y + 18.0
# Background
draw_rect(Rect2(bx2, by2, bar_w, bar_h), Color(0.0, 0.04, 0.02, 0.7))
# Fill — remaining cooldown shown as shrinking bar (ratio 1 = full cd, 0 = ready)
var fill_w: float = bar_w * (1.0 - ratio)
var is_ready: bool = ratio <= 0.0
var fill_col: Color
if is_ready:
fill_col = Color(1.0, 0.9, 0.3, 0.7 + 0.3 * sin(hud.blink_phase * 6.0))
else:
fill_col = Color(COL_WARNING.r, COL_WARNING.g, COL_WARNING.b, 0.75)
draw_rect(Rect2(bx2, by2, fill_w, bar_h), fill_col)
# Border
draw_rect(Rect2(bx2, by2, bar_w, bar_h),
Color(COL_DIM.r, COL_DIM.g, COL_DIM.b, 0.7), false, 1.0)
# Big wipe warning — bold centred flash, no decoration
if hud.wipe_warning:
if sin(hud.blink_phase * 8.0) > 0.0:
_draw_text_centered(font, Tr.hint("hud_wipe_key"), W * 0.5, H * 0.5 - 8.0, 13, COL_WARNING)
return
# ── Gameover / Returning ─────────────────────────────────────────────────
if hud.is_gameover or hud.is_returning:
# Dim overlay
draw_rect(get_viewport_rect(), Color(0.0, 0.0, 0.04, 0.62))
var sd: Dictionary = hud.score_data
var p1_time: float = sd.get("p1_time", 0.0)
var p1_kills: int = sd.get("p1_kills", 0)
var total: int = int(p1_time) + p1_kills * 50 + int(sd.get("p1_wipe", 0))
if sd.get("multiplayer", false):
total += int(sd.get("p2_time", 0.0)) + int(sd.get("p2_kills", 0)) * 50 + int(sd.get("p2_wipe", 0))
# Central terminal box — taller when asking for name
var bx := W * 0.5 - 160.0
var by := H * 0.5 - 100.0
var bw := 320.0
var bh2 := 215.0 if hud.awaiting_name else 200.0
_draw_terminal_box(bx, by, bw, bh2, COL_PRIMARY)
_draw_text_centered(font, Tr.t("hud_mission_over"), W * 0.5, by + 22.0, 12, COL_ACCENT)
_draw_hline(bx + 16.0, bx + bw - 16.0, by + 42.0, COL_DIM)
_draw_text_centered(font, Tr.t("hud_score") % total, W * 0.5, by + 60.0, 18, COL_WHITE)
var mins1: int = int(p1_time / 60.0)
var secs1: int = int(p1_time) % 60
_draw_text_centered(font, "P1 %02d:%02d KILLS %d" % [mins1, secs1, p1_kills],
W * 0.5, by + 92.0, 10, COL_PRIMARY)
if sd.get("multiplayer", false):
var p2t: float = sd.get("p2_time", 0.0)
var mins2: int = int(p2t / 60.0); var secs2: int = int(p2t) % 60
_draw_text_centered(font, "P2 %02d:%02d KILLS %d" % [mins2, secs2, sd.get("p2_kills", 0)],
W * 0.5, by + 112.0, 10, Color(0.27, 1.0, 0.67, 0.8))
_draw_hline(bx + 16.0, bx + bw - 16.0, by + 135.0, COL_DIM)
if hud.is_returning:
_draw_text_centered(font, Tr.t("hud_transfer"), W * 0.5, by + 155.0, 10, COL_WARNING)
elif hud.awaiting_name:
_draw_text_centered(font, Tr.t("lb_enter_name"), W * 0.5, by + 148.0, 10, COL_DIM)
var cursor_vis: bool = fmod(hud.blink_phase, 1.0) < 0.6
var field_str: String = hud.name_input + ("_" if cursor_vis else " ")
var display_str: String = field_str if field_str.strip_edges().length() > 0 else "_"
_draw_text_centered(font, display_str, W * 0.5, by + 163.0, 13, COL_ACCENT)
_draw_text_centered(font, Tr.hint("lb_name_hint"), W * 0.5, by + 186.0, 8, COL_DIM)
elif hud.name_saved:
_draw_text_centered(font, Tr.t("lb_saved"), W * 0.5, by + 155.0, 11, COL_ACCENT)
elif hud.gameover_blink:
_draw_text_centered(font, Tr.hint("hud_press_key"), W * 0.5, by + 155.0, 11, COL_ACCENT)
# ── Drawing helpers ───────────────────────────────────────────────────────────
func _draw_corner_brackets() -> void:
# Four L-shaped corner brackets as the only permanent chrome
var m := MARGIN
var bl := BRACKET_LEN
var bw := BRACKET_W
# Top-left
draw_line(Vector2(m, m), Vector2(m + bl, m), COL_DIM, bw)
draw_line(Vector2(m, m), Vector2(m, m + bl), COL_DIM, bw)
# Top-right
draw_line(Vector2(W-m, m), Vector2(W-m-bl, m), COL_DIM, bw)
draw_line(Vector2(W-m, m), Vector2(W-m, m+bl), COL_DIM, bw)
# Bottom-left
draw_line(Vector2(m, H-m), Vector2(m+bl, H-m), COL_DIM, bw)
draw_line(Vector2(m, H-m), Vector2(m, H-m-bl), COL_DIM, bw)
# Bottom-right
draw_line(Vector2(W-m, H-m), Vector2(W-m-bl, H-m), COL_DIM, bw)
draw_line(Vector2(W-m, H-m), Vector2(W-m, H-m-bl), COL_DIM, bw)
func _draw_terminal_box(bx: float, by: float, bw: float, bh2: float, col: Color) -> void:
# Filled dark panel
draw_rect(Rect2(bx, by, bw, bh2), Color(0.0, 0.04, 0.02, 0.88))
# Border lines
var bc := Color(col.r, col.g, col.b, col.a * 0.6)
draw_line(Vector2(bx, by), Vector2(bx+bw, by), bc, 1.0)
draw_line(Vector2(bx, by+bh2), Vector2(bx+bw, by+bh2), bc, 1.0)
draw_line(Vector2(bx, by), Vector2(bx, by+bh2), bc, 1.0)
draw_line(Vector2(bx+bw, by), Vector2(bx+bw, by+bh2), bc, 1.0)
# Corner L-brackets (bright)
var cl := 10.0
for cx in [bx, bx+bw]:
for cy in [by, by+bh2]:
var sx := 1.0 if cx == bx else -1.0
var sy := 1.0 if cy == by else -1.0
draw_line(Vector2(cx, cy), Vector2(cx + sx*cl, cy), col, 1.5)
draw_line(Vector2(cx, cy), Vector2(cx, cy + sy*cl), col, 1.5)
func _draw_hline(x1: float, x2: float, y: float, col: Color) -> void:
draw_line(Vector2(x1, y), Vector2(x2, y), col, 1.0)
func _draw_text(font: Font, text: String, x: float, y: float, size: int, col: Color) -> void:
draw_string(font, Vector2(x, y + size), text, HORIZONTAL_ALIGNMENT_LEFT, -1, size, col)
func _draw_text_centered(font: Font, text: String, x: float, y: float, size: int, col: Color) -> void:
var tw := font.get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, size).x
draw_string(font, Vector2(x - tw * 0.5, y + size), text, HORIZONTAL_ALIGNMENT_LEFT, -1, size, col)