Nicht abgenommen: Nach dem ersten Spieltest wurde entschieden, Champions of Luneraria nach Unity 6 zu portieren. Dieser Stand ist die Referenz für das HUD im Unity-Projekt (../luneraria-unity). - HUD neu aus Komponenten mit eigenem Theme, Porträts aus den echten Modellen (einmal gerendert, zwischengespeichert), Lane-Übersicht mit deterministischer Bedrohung aus öffentlichem Zustand, Bossgrotte, nächste Welle, Ereignisse, Minimap mit zusammengefassten Armeen, Held/Items/Talente, Einheitendetails, Angriffschampions, Bereit-Sperre, UI-Größe, Team-Formen statt nur Farben - Privatsphäre über LuneHudModel: fremdes Gold und fremde Entwürfe erscheinen nie - Keine Regeländerung: Regeln 91/91, M2 73/73, Netz 43/43; verify_g3 715/715; verify_m1 38/38 und verify_m2 14/14 mit angepassten UI-Treibern - Spieltest bis Welle 8 über die echte Oberfläche, Befunde in G3-HUD.md - HELDEN-KONZEPT.md: vom Projektinhaber vorgegebene Helden (nicht umgesetzt) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
282 lines
15 KiB
GDScript
282 lines
15 KiB
GDScript
class_name LuneGlyphs
|
|
extends RefCounted
|
|
## Vector symbols for the HUD, drawn from a unit square so they stay sharp at every UI scale
|
|
## and resolution. Team emblems follow the G2 world: Blau = Mondsichel (round shapes),
|
|
## Rot = Sonnenscheibe (rayed, angular shapes), the grotto = hexagon. Symbols are drawn
|
|
## with draw_* calls into any CanvasItem; nothing here allocates nodes.
|
|
|
|
const NAMES: Array[String] = ["coin", "emblem_0", "emblem_1", "emblem_2", "shape_0", "shape_1", "shape_2",
|
|
"tank", "melee", "ranged", "splash", "support", "siege", "crown", "champion", "hourglass", "tower",
|
|
"ancient", "warning", "check", "close", "bot", "offline", "clock", "heart", "tempo", "range", "armor",
|
|
"move", "star", "lock", "up", "lane_top", "lane_mid", "lane_bot", "grotto", "pause", "play", "menu",
|
|
"gear", "human", "sword", "basaltschild", "glutklinge", "rankenmantel", "funkenfokus", "quellamulett",
|
|
"kristallsplitter", "hero", "skull"]
|
|
|
|
static var _cache: Dictionary = {}
|
|
|
|
## Fits the unit square into rect (centred, aspect kept).
|
|
static func _frame(rect: Rect2) -> Transform2D:
|
|
var s: float = minf(rect.size.x, rect.size.y)
|
|
var origin: Vector2 = rect.position + (rect.size - Vector2(s, s)) * .5
|
|
return Transform2D(0.0, Vector2(s, s), 0.0, origin)
|
|
|
|
static func _pts(frame: Transform2D, points: Array) -> PackedVector2Array:
|
|
var result := PackedVector2Array()
|
|
for p: Vector2 in points:
|
|
result.append(frame * p)
|
|
return result
|
|
|
|
static func _rot(points: Array, angle: float, pivot: Vector2 = Vector2(.5, .5)) -> Array:
|
|
var result: Array = []
|
|
for p: Vector2 in points:
|
|
result.append(pivot + (p - pivot).rotated(angle))
|
|
return result
|
|
|
|
static func _fill(ci: CanvasItem, points: PackedVector2Array, color: Color) -> void:
|
|
if points.size() < 3:
|
|
return
|
|
ci.draw_colored_polygon(points, color)
|
|
var outline := points.duplicate()
|
|
outline.append(points[0])
|
|
ci.draw_polyline(outline, color, 1.0, true)
|
|
|
|
static func _line(ci: CanvasItem, frame: Transform2D, points: Array, color: Color, width: float) -> void:
|
|
ci.draw_polyline(_pts(frame, points), color, maxf(1.0, width * frame.x.x), true)
|
|
|
|
static func _circle(points: int, center: Vector2, radius: float, start: float = 0.0) -> Array:
|
|
var result: Array = []
|
|
for index: int in range(points):
|
|
var angle: float = start + TAU * float(index) / float(points)
|
|
result.append(center + Vector2(cos(angle), sin(angle)) * radius)
|
|
return result
|
|
|
|
static func _star(points: int, outer: float, inner: float, start: float = -PI * .5) -> Array:
|
|
var result: Array = []
|
|
for index: int in range(points * 2):
|
|
var angle: float = start + PI * float(index) / float(points)
|
|
result.append(Vector2(.5, .5) + Vector2(cos(angle), sin(angle)) * (outer if index % 2 == 0 else inner))
|
|
return result
|
|
|
|
## Crescent (Blau): outer disc minus an offset disc, computed once.
|
|
static func _crescent() -> Array:
|
|
if not _cache.has("crescent"):
|
|
var outer := PackedVector2Array(_circle(48, Vector2(.5, .5), .42))
|
|
var inner := PackedVector2Array(_circle(48, Vector2(.66, .4), .33))
|
|
var clipped: Array = Geometry2D.clip_polygons(outer, inner)
|
|
var result: Array = []
|
|
if not clipped.is_empty():
|
|
for p: Vector2 in clipped[0]:
|
|
result.append(p)
|
|
_cache["crescent"] = result
|
|
return _cache["crescent"]
|
|
|
|
static func _heart() -> Array:
|
|
if not _cache.has("heart"):
|
|
var result: Array = []
|
|
for index: int in range(40):
|
|
var t: float = TAU * float(index) / 40.0
|
|
var x: float = 16.0 * pow(sin(t), 3)
|
|
var y: float = 13.0 * cos(t) - 5.0 * cos(2.0 * t) - 2.0 * cos(3.0 * t) - cos(4.0 * t)
|
|
result.append(Vector2(.5 + x / 38.0, .47 - y / 38.0))
|
|
_cache["heart"] = result
|
|
return _cache["heart"]
|
|
|
|
static func _drop() -> Array:
|
|
var result: Array = [Vector2(.5, .16)]
|
|
for index: int in range(17):
|
|
var angle: float = -PI * .15 + PI * 1.3 * float(index) / 16.0
|
|
result.append(Vector2(.5, .64) + Vector2(cos(angle), sin(angle)) * .22)
|
|
return result
|
|
|
|
## Draws one symbol. `accent` is an optional second colour (inner details); by default a
|
|
## darkened version of `color`.
|
|
static func draw(ci: CanvasItem, glyph: String, rect: Rect2, color: Color, accent: Color = Color(0, 0, 0, 0)) -> void:
|
|
var f: Transform2D = _frame(rect)
|
|
var dark: Color = accent if accent.a > 0.0 else Color(color.darkened(.55), color.a)
|
|
match glyph:
|
|
"coin":
|
|
var c: Vector2 = f * Vector2(.5, .5)
|
|
var r: float = f.x.x * .42
|
|
ci.draw_circle(c, r, color)
|
|
ci.draw_arc(c, r, 0, TAU, 32, color.darkened(.35), maxf(1.0, r * .12), true)
|
|
ci.draw_arc(c, r * .62, 0, TAU, 24, color.darkened(.28), maxf(1.0, r * .1), true)
|
|
ci.draw_arc(c, r * .8, PI * 1.1, PI * 1.45, 8, Color(1, 1, 1, .55 * color.a), maxf(1.0, r * .1), true)
|
|
"emblem_0":
|
|
_fill(ci, _pts(f, _crescent()), color)
|
|
"emblem_1":
|
|
ci.draw_circle(f * Vector2(.5, .5), f.x.x * .2, color)
|
|
for index: int in range(8):
|
|
var angle: float = TAU * float(index) / 8.0 - PI * .5
|
|
var tip := Vector2(.5, .5) + Vector2(cos(angle), sin(angle)) * .47
|
|
var left := Vector2(.5, .5) + Vector2(cos(angle - .22), sin(angle - .22)) * .27
|
|
var right := Vector2(.5, .5) + Vector2(cos(angle + .22), sin(angle + .22)) * .27
|
|
_fill(ci, _pts(f, [left, tip, right]), color)
|
|
"emblem_2":
|
|
var hexagon: Array = _circle(6, Vector2(.5, .5), .42, -PI * .5)
|
|
hexagon.append(hexagon[0])
|
|
_line(ci, f, hexagon, color, .08)
|
|
_fill(ci, _pts(f, [Vector2(.5, .3), Vector2(.64, .5), Vector2(.5, .7), Vector2(.36, .5)]), color)
|
|
"shape_0":
|
|
ci.draw_circle(f * Vector2(.5, .5), f.x.x * .4, color)
|
|
"shape_1":
|
|
_fill(ci, _pts(f, [Vector2(.5, .06), Vector2(.94, .5), Vector2(.5, .94), Vector2(.06, .5)]), color)
|
|
"shape_2":
|
|
_fill(ci, _pts(f, _circle(6, Vector2(.5, .5), .44, -PI * .5)), color)
|
|
"tank", "armor":
|
|
var shield: Array = [Vector2(.2, .16), Vector2(.8, .16), Vector2(.8, .48), Vector2(.72, .66), Vector2(.5, .87), Vector2(.28, .66), Vector2(.2, .48)]
|
|
if glyph == "tank":
|
|
_fill(ci, _pts(f, shield), color)
|
|
_line(ci, f, [Vector2(.5, .24), Vector2(.5, .74)], dark, .07)
|
|
else:
|
|
shield.append(shield[0])
|
|
_line(ci, f, shield, color, .09)
|
|
"melee", "sword":
|
|
var blade: Array = [Vector2(.5, .06), Vector2(.56, .14), Vector2(.56, .62), Vector2(.44, .62), Vector2(.44, .14)]
|
|
_fill(ci, _pts(f, _rot(blade, PI * .25)), color)
|
|
_fill(ci, _pts(f, _rot([Vector2(.3, .62), Vector2(.7, .62), Vector2(.7, .69), Vector2(.3, .69)], PI * .25)), color)
|
|
_fill(ci, _pts(f, _rot([Vector2(.46, .69), Vector2(.54, .69), Vector2(.54, .88), Vector2(.46, .88)], PI * .25)), color)
|
|
"ranged":
|
|
var bow: Array = []
|
|
for index: int in range(13):
|
|
var angle: float = PI * .62 + PI * .76 * float(index) / 12.0
|
|
bow.append(Vector2(.78, .5) + Vector2(cos(angle), sin(angle)) * .5)
|
|
_line(ci, f, _rot(bow, -PI * .25), color, .08)
|
|
_line(ci, f, _rot([bow[0], bow[12]], -PI * .25), color, .03)
|
|
_line(ci, f, _rot([Vector2(.14, .5), Vector2(.82, .5)], -PI * .25), color, .06)
|
|
_fill(ci, _pts(f, _rot([Vector2(.9, .5), Vector2(.76, .41), Vector2(.76, .59)], -PI * .25)), color)
|
|
"splash":
|
|
_fill(ci, _pts(f, _star(8, .46, .2)), color)
|
|
ci.draw_circle(f * Vector2(.5, .5), f.x.x * .1, dark)
|
|
"support":
|
|
_fill(ci, _pts(f, [Vector2(.4, .14), Vector2(.6, .14), Vector2(.6, .4), Vector2(.86, .4), Vector2(.86, .6), Vector2(.6, .6),
|
|
Vector2(.6, .86), Vector2(.4, .86), Vector2(.4, .6), Vector2(.14, .6), Vector2(.14, .4), Vector2(.4, .4)]), color)
|
|
"siege":
|
|
_fill(ci, _pts(f, _rot([Vector2(.18, .18), Vector2(.74, .18), Vector2(.74, .42), Vector2(.18, .42)], -PI * .2)), color)
|
|
_fill(ci, _pts(f, _rot([Vector2(.42, .4), Vector2(.52, .4), Vector2(.52, .92), Vector2(.42, .92)], -PI * .2)), color)
|
|
"crown", "hero":
|
|
_fill(ci, _pts(f, [Vector2(.12, .74), Vector2(.12, .3), Vector2(.32, .5), Vector2(.5, .2), Vector2(.68, .5), Vector2(.88, .3), Vector2(.88, .74)]), color)
|
|
_fill(ci, _pts(f, [Vector2(.12, .78), Vector2(.88, .78), Vector2(.88, .88), Vector2(.12, .88)]), color)
|
|
if glyph == "crown":
|
|
ci.draw_circle(f * Vector2(.5, .6), f.x.x * .06, dark)
|
|
"skull":
|
|
_fill(ci, _pts(f, _circle(20, Vector2(.5, .44), .34)), color)
|
|
_fill(ci, _pts(f, [Vector2(.32, .6), Vector2(.68, .6), Vector2(.64, .86), Vector2(.36, .86)]), color)
|
|
ci.draw_circle(f * Vector2(.37, .45), f.x.x * .09, dark)
|
|
ci.draw_circle(f * Vector2(.63, .45), f.x.x * .09, dark)
|
|
"champion":
|
|
_fill(ci, _pts(f, [Vector2(.5, .06), Vector2(.82, .46), Vector2(.61, .46), Vector2(.61, .9), Vector2(.39, .9), Vector2(.39, .46), Vector2(.18, .46)]), color)
|
|
"hourglass":
|
|
_fill(ci, _pts(f, [Vector2(.24, .12), Vector2(.76, .12), Vector2(.56, .5), Vector2(.44, .5)]), color)
|
|
_fill(ci, _pts(f, [Vector2(.44, .5), Vector2(.56, .5), Vector2(.76, .88), Vector2(.24, .88)]), color)
|
|
_line(ci, f, [Vector2(.18, .1), Vector2(.82, .1)], color, .06)
|
|
_line(ci, f, [Vector2(.18, .9), Vector2(.82, .9)], color, .06)
|
|
"tower":
|
|
_fill(ci, _pts(f, [Vector2(.28, .92), Vector2(.28, .36), Vector2(.22, .36), Vector2(.22, .14), Vector2(.33, .14), Vector2(.33, .23),
|
|
Vector2(.44, .23), Vector2(.44, .14), Vector2(.56, .14), Vector2(.56, .23), Vector2(.67, .23), Vector2(.67, .14),
|
|
Vector2(.78, .14), Vector2(.78, .36), Vector2(.72, .36), Vector2(.72, .92)]), color)
|
|
_fill(ci, _pts(f, [Vector2(.44, .92), Vector2(.44, .72), Vector2(.5, .66), Vector2(.56, .72), Vector2(.56, .92)]), dark)
|
|
"ancient":
|
|
_fill(ci, _pts(f, [Vector2(.5, .05), Vector2(.76, .4), Vector2(.6, .95), Vector2(.4, .95), Vector2(.24, .4)]), color)
|
|
_line(ci, f, [Vector2(.5, .05), Vector2(.5, .95)], dark, .04)
|
|
_line(ci, f, [Vector2(.24, .4), Vector2(.5, .52), Vector2(.76, .4)], dark, .04)
|
|
"warning":
|
|
_fill(ci, _pts(f, [Vector2(.5, .08), Vector2(.94, .88), Vector2(.06, .88)]), color)
|
|
_fill(ci, _pts(f, [Vector2(.46, .34), Vector2(.54, .34), Vector2(.53, .64), Vector2(.47, .64)]), dark)
|
|
ci.draw_circle(f * Vector2(.5, .75), f.x.x * .05, dark)
|
|
"check":
|
|
_line(ci, f, [Vector2(.16, .52), Vector2(.4, .76), Vector2(.86, .24)], color, .13)
|
|
"close":
|
|
_line(ci, f, [Vector2(.2, .2), Vector2(.8, .8)], color, .12)
|
|
_line(ci, f, [Vector2(.8, .2), Vector2(.2, .8)], color, .12)
|
|
"bot", "gear":
|
|
var c: Vector2 = f * Vector2(.5, .5)
|
|
for index: int in range(8):
|
|
var angle: float = TAU * float(index) / 8.0
|
|
_fill(ci, _pts(f, _rot([Vector2(.43, .06), Vector2(.57, .06), Vector2(.57, .24), Vector2(.43, .24)], angle)), color)
|
|
ci.draw_circle(c, f.x.x * .3, color)
|
|
ci.draw_circle(c, f.x.x * .12, dark)
|
|
"human":
|
|
ci.draw_circle(f * Vector2(.5, .3), f.x.x * .17, color)
|
|
_fill(ci, _pts(f, [Vector2(.2, .9), Vector2(.24, .62), Vector2(.38, .52), Vector2(.62, .52), Vector2(.76, .62), Vector2(.8, .9)]), color)
|
|
"offline":
|
|
ci.draw_arc(f * Vector2(.5, .5), f.x.x * .38, 0, TAU, 32, color, maxf(1.0, f.x.x * .1), true)
|
|
_line(ci, f, [Vector2(.22, .78), Vector2(.78, .22)], color, .1)
|
|
"clock":
|
|
ci.draw_arc(f * Vector2(.5, .5), f.x.x * .4, 0, TAU, 32, color, maxf(1.0, f.x.x * .09), true)
|
|
_line(ci, f, [Vector2(.5, .24), Vector2(.5, .5), Vector2(.68, .62)], color, .08)
|
|
"heart":
|
|
_fill(ci, _pts(f, _heart()), color)
|
|
"tempo":
|
|
_fill(ci, _pts(f, [Vector2(.6, .04), Vector2(.22, .56), Vector2(.46, .56), Vector2(.38, .96), Vector2(.78, .42), Vector2(.54, .42), Vector2(.66, .04)]), color)
|
|
"range":
|
|
var c: Vector2 = f * Vector2(.5, .5)
|
|
ci.draw_arc(c, f.x.x * .38, 0, TAU, 32, color, maxf(1.0, f.x.x * .08), true)
|
|
ci.draw_arc(c, f.x.x * .18, 0, TAU, 24, color, maxf(1.0, f.x.x * .08), true)
|
|
ci.draw_circle(c, f.x.x * .06, color)
|
|
for tick: Array in [[Vector2(.5, .02), Vector2(.5, .16)], [Vector2(.5, .84), Vector2(.5, .98)], [Vector2(.02, .5), Vector2(.16, .5)], [Vector2(.84, .5), Vector2(.98, .5)]]:
|
|
_line(ci, f, tick, color, .07)
|
|
"move":
|
|
_line(ci, f, [Vector2(.18, .2), Vector2(.46, .5), Vector2(.18, .8)], color, .11)
|
|
_line(ci, f, [Vector2(.52, .2), Vector2(.8, .5), Vector2(.52, .8)], color, .11)
|
|
"star":
|
|
_fill(ci, _pts(f, _star(5, .47, .2)), color)
|
|
"lock":
|
|
_fill(ci, _pts(f, [Vector2(.22, .46), Vector2(.78, .46), Vector2(.78, .9), Vector2(.22, .9)]), color)
|
|
ci.draw_arc(f * Vector2(.5, .46), f.x.x * .19, PI, TAU, 16, color, maxf(1.0, f.x.x * .09), true)
|
|
ci.draw_circle(f * Vector2(.5, .64), f.x.x * .06, dark)
|
|
"up":
|
|
_fill(ci, _pts(f, [Vector2(.5, .08), Vector2(.86, .48), Vector2(.62, .48), Vector2(.62, .9), Vector2(.38, .9), Vector2(.38, .48), Vector2(.14, .48)]), color)
|
|
"lane_top", "lane_mid", "lane_bot":
|
|
var box: Array = [Vector2(.12, .12), Vector2(.88, .12), Vector2(.88, .88), Vector2(.12, .88), Vector2(.12, .12)]
|
|
_line(ci, f, box, Color(color, color.a * .3), .05)
|
|
var path: Array = [Vector2(.2, .8), Vector2(.2, .2), Vector2(.8, .2)] if glyph == "lane_top" else ([Vector2(.2, .8), Vector2(.8, .2)] if glyph == "lane_mid" else [Vector2(.2, .8), Vector2(.8, .8), Vector2(.8, .2)])
|
|
_line(ci, f, path, color, .12)
|
|
"grotto":
|
|
var arch: Array = [Vector2(.08, .9)]
|
|
for index: int in range(13):
|
|
var angle: float = PI + PI * float(index) / 12.0
|
|
arch.append(Vector2(.5, .62) + Vector2(cos(angle) * .42, sin(angle) * .5))
|
|
arch.append(Vector2(.92, .9))
|
|
_fill(ci, _pts(f, arch), color)
|
|
var mouth: Array = [Vector2(.3, .9)]
|
|
for index: int in range(9):
|
|
var angle: float = PI + PI * float(index) / 8.0
|
|
mouth.append(Vector2(.5, .72) + Vector2(cos(angle) * .2, sin(angle) * .26))
|
|
mouth.append(Vector2(.7, .9))
|
|
_fill(ci, _pts(f, mouth), dark)
|
|
"pause":
|
|
_fill(ci, _pts(f, [Vector2(.24, .16), Vector2(.42, .16), Vector2(.42, .84), Vector2(.24, .84)]), color)
|
|
_fill(ci, _pts(f, [Vector2(.58, .16), Vector2(.76, .16), Vector2(.76, .84), Vector2(.58, .84)]), color)
|
|
"play":
|
|
_fill(ci, _pts(f, [Vector2(.26, .14), Vector2(.84, .5), Vector2(.26, .86)]), color)
|
|
"menu":
|
|
for y: float in [.24, .5, .76]:
|
|
_line(ci, f, [Vector2(.16, y), Vector2(.84, y)], color, .1)
|
|
"basaltschild":
|
|
_fill(ci, _pts(f, _circle(6, Vector2(.5, .5), .44, -PI * .5)), color)
|
|
var inner: Array = _circle(6, Vector2(.5, .5), .27, -PI * .5)
|
|
inner.append(inner[0])
|
|
_line(ci, f, inner, dark, .07)
|
|
"glutklinge":
|
|
var blade: Array = [Vector2(.5, .06), Vector2(.57, .15), Vector2(.57, .66), Vector2(.43, .66), Vector2(.43, .15)]
|
|
_fill(ci, _pts(f, _rot(blade, PI * .25)), color)
|
|
_fill(ci, _pts(f, _rot([Vector2(.3, .66), Vector2(.7, .66), Vector2(.7, .73), Vector2(.3, .73)], PI * .25)), color)
|
|
_fill(ci, _pts(f, [Vector2(.2, .5), Vector2(.3, .3), Vector2(.36, .42), Vector2(.42, .22), Vector2(.5, .44), Vector2(.4, .6)]), dark if accent.a > 0.0 else Color("ffb070"))
|
|
"rankenmantel":
|
|
_fill(ci, _pts(f, [Vector2(.5, .1), Vector2(.66, .18), Vector2(.8, .9), Vector2(.5, .8), Vector2(.2, .9), Vector2(.34, .18)]), color)
|
|
_line(ci, f, [Vector2(.5, .22), Vector2(.42, .4), Vector2(.56, .56), Vector2(.46, .74)], dark, .05)
|
|
"funkenfokus":
|
|
ci.draw_arc(f * Vector2(.5, .5), f.x.x * .42, 0, TAU, 32, color, maxf(1.0, f.x.x * .08), true)
|
|
_fill(ci, _pts(f, [Vector2(.56, .16), Vector2(.3, .54), Vector2(.47, .54), Vector2(.42, .84), Vector2(.7, .44), Vector2(.53, .44), Vector2(.62, .16)]), color)
|
|
"quellamulett":
|
|
ci.draw_arc(f * Vector2(.5, .2), f.x.x * .14, PI * .9, PI * 2.1, 12, color, maxf(1.0, f.x.x * .06), true)
|
|
_fill(ci, _pts(f, _drop()), color)
|
|
ci.draw_circle(f * Vector2(.44, .66), f.x.x * .06, Color(1, 1, 1, .45 * color.a))
|
|
"kristallsplitter":
|
|
for spec: Array in [[Vector2(.5, .5), 0.0, .44], [Vector2(.3, .62), -.55, .3], [Vector2(.7, .62), .55, .3]]:
|
|
var center: Vector2 = spec[0]
|
|
var length: float = spec[2]
|
|
var shard: Array = [center + Vector2(0, -length), center + Vector2(.08, 0), center + Vector2(0, length * .45), center + Vector2(-.08, 0)]
|
|
_fill(ci, _pts(f, _rot(shard, spec[1], center)), color)
|
|
_:
|
|
ci.draw_circle(f * Vector2(.5, .5), f.x.x * .3, color)
|