Files
elements-autochess/scripts/ui/HudOverlay.gd
2026-09-16 13:21:45 +02:00

135 lines
4.9 KiB
GDScript

class_name HudOverlay
extends Control
## Zeichnet Lebens- und Manabalken, Rolle, Sterne und Schadenszahlen
## über die 3D-Einheiten. Spielt außerdem die Kampfgeräusche ab.
const FLOAT_TIME := 0.9
const FLOAT_RISE := 42.0
var board: BoardView3D = null
var sfx: Sfx = null
## Tooltip zu einer Einheit, gesetzt von Main (Maus über Einheit oder Shop-Karte).
var tooltip_avatar: Avatar = null
var tooltip_pos := Vector2.ZERO
var tooltip_note := ""
var _font: Font
var _floaters: Array = [] ## {"world": Vector3, "text": String, "col": Color, "age": float}
var _combat_seen: Combat = null
func _ready() -> void:
_font = ThemeDB.fallback_font
mouse_filter = Control.MOUSE_FILTER_IGNORE
set_process(true)
func _process(delta: float) -> void:
_pull_hits()
for f in _floaters:
f["age"] += delta
_floaters = _floaters.filter(func(f): return f["age"] < FLOAT_TIME)
queue_redraw()
## Holt neue Treffer aus dem laufenden Spielerkampf und macht Zahlen und Töne daraus.
func _pull_hits() -> void:
if board == null or board.game == null:
return
var c: Combat = board.game.player_combat
if c == null or not c.record_hits:
_combat_seen = null
return
if c != _combat_seen:
_combat_seen = c
_floaters.clear()
if c.hits.is_empty():
return
var my_team := board.game.human_team_in_combat()
for h in c.hits:
var world := board.world_pos(board.mirror_for_view(h["pos"], my_team))
var mine: bool = h["team"] == my_team
match h["kind"]:
"dmg":
_floaters.append({"world": world, "text": str(h["amount"]),
"col": Color(1.0, 0.55, 0.45) if mine else Color(1.0, 0.95, 0.85), "age": 0.0})
if sfx: sfx.play("treffer")
"crit":
_floaters.append({"world": world, "text": str(h["amount"]) + "!",
"col": Color(1.0, 0.85, 0.3), "age": 0.0})
if sfx: sfx.play("krit")
"counter":
# Konter-Treffer: Element oder Rolle war stark gegen das Ziel.
_floaters.append({"world": world, "text": str(h["amount"]) + " ▲",
"col": Color(1.0, 0.6, 0.9), "age": 0.0})
if sfx: sfx.play("krit")
"heal":
_floaters.append({"world": world, "text": "+" + str(h["amount"]),
"col": Color(0.55, 1.0, 0.6), "age": 0.0})
if sfx: sfx.play("heilung")
"cast":
if sfx: sfx.play("zauber")
var tgt := board.world_pos(board.mirror_for_view(h["target"], my_team))
board.spawn_effect(world, tgt, h["role"], Data.ELEMENT_COLORS[h["element"]])
"attack":
var from := board.world_pos(board.mirror_for_view(h["from"], my_team))
board.spawn_attack(from, world, h["ranged"], Data.ELEMENT_COLORS[h["element"]])
c.hits.clear()
func _draw() -> void:
if board == null or board.camera == null:
return
var cam := board.camera
for d in board.draw_units:
var world: Vector3 = d["pos"]
if cam.is_position_behind(world):
continue
var head := cam.unproject_position(world + Vector3(0.0, 1.35, 0.0))
var foot := cam.unproject_position(world + Vector3(0.0, 0.05, 0.0))
var a: Avatar = d["avatar"]
var side: int = d["side"]
var bw: float = clampf(absf(foot.y - head.y) * 0.85, 26.0, 70.0)
var bx := head.x - bw * 0.5
var by := head.y - 4.0
draw_rect(Rect2(bx - 1.0, by - 1.0, bw + 2.0, 8.0), Color(0.05, 0.05, 0.07, 0.85))
draw_rect(Rect2(bx, by, bw * clampf(d["hp"], 0.0, 1.0), 6.0),
Color(0.35, 0.85, 0.42) if side == 0 else Color(0.92, 0.38, 0.38))
if d["mana"] > 0.0:
draw_rect(Rect2(bx, by + 7.0, bw, 4.0), Color(0.05, 0.05, 0.07, 0.85))
draw_rect(Rect2(bx, by + 7.0, bw * clampf(d["mana"], 0.0, 1.0), 4.0),
Color(0.35, 0.62, 0.95))
# Schild als heller Streifen über dem Lebensbalken.
if d.get("shield", 0.0) > 0.0:
draw_rect(Rect2(bx, by - 4.0, bw * clampf(d["shield"], 0.0, 1.0), 3.0), Color(0.85, 0.9, 1.0))
for s in a.star:
draw_circle(Vector2(bx + bw * 0.5 - 7.0 + 7.0 * float(s), by - 9.0), 2.6,
Color(1.0, 0.85, 0.25))
draw_string(_font, Vector2(bx + bw + 4.0, by + 7.0), Data.ROLE_SHORT[a.role],
HORIZONTAL_ALIGNMENT_LEFT, -1, 13, Color(0.9, 0.9, 0.95, 0.9))
if d["cast"]:
draw_arc(foot, bw * 0.55, 0.0, TAU, 24, Color(1.0, 0.95, 0.5, 0.9), 2.5)
# Zustände als kleine Marken neben dem Balken.
var marks := []
if d.get("burn", false): marks.append(["B", Color(1.0, 0.5, 0.2)])
if d.get("slow", false): marks.append(["S", Color(0.6, 0.85, 1.0)])
if d.get("stun", false): marks.append(["Z", Color(1.0, 0.9, 0.3)])
for i in marks.size():
draw_string(_font, Vector2(bx - 12.0 - 11.0 * float(i), by + 7.0), marks[i][0],
HORIZONTAL_ALIGNMENT_LEFT, -1, 12, marks[i][1])
# Schwebende Zahlen
for f in _floaters:
var t: float = f["age"] / FLOAT_TIME
var pos := cam.unproject_position(f["world"] + Vector3(0.0, 1.5, 0.0))
pos.y -= t * FLOAT_RISE
var col: Color = f["col"]
col.a = 1.0 - t * t
var size := 15 if f["text"].ends_with("!") else 13
draw_string(_font, pos + Vector2(1, 1), f["text"], HORIZONTAL_ALIGNMENT_CENTER, -1, size,
Color(0, 0, 0, col.a * 0.8))
draw_string(_font, pos, f["text"], HORIZONTAL_ALIGNMENT_CENTER, -1, size, col)