HUD de videojuego
Un HUD completo sobre una nebulosa animada. Todo son widgets normales de Pyzed: la nebulosa y el barrido del radar son grafos de shader (archivos .pzshad hechos con nodos en Pyzed Design), los paneles son glass, y la habilidad lista y el cartel de subida de nivel usan bloom.

uv run --with pyzed-ui python examples/showcase/hud.pypython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-uipython examples/showcase/hud.pypy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-uipython examples\showcase\hud.pypython3 examples/showcase/hud.pypy examples\showcase\hud.pyPulsa de 1 a 6 (o haz clic en una casilla) para lanzar una habilidad.
Cómo está hecho
Sección titulada «Cómo está hecho»Fondo con shader
Sección titulada «Fondo con shader»La nebulosa es un grafo de ruido deformado que la GPU redibuja en cada fotograma. El barrido del radar es otro grafo
dentro de un panel de cristal redondo; su variable accent fija el color desde Python.
SHADERS = Path(__file__).parent / "shaders"pz.shader(SHADERS / "nebula.pzshad", width="fill", height="fill", fallback=pz.Gradient(["#05040f", "#2a0c46", "#0b3a4a"], angle=135))pz.shader(SHADERS / "radar.pzshad", uniforms={"accent": "#34d399"}, width=176, height=176)Mira los dos grafos en Shaders y efectos.
Paneles de cristal
Sección titulada «Paneles de cristal»Cada panel desenfoca y tiñe lo que tiene detrás.
pz.row([...]).style(glass={"tint": "#0f172a", "strength": 0.35, "blur": 18}, radius=16)Luz y movimiento
Sección titulada «Luz y movimiento»La vida baja palpita; la habilidad definitiva brilla con bloom y late cuando está lista.
health.animate("glow_pulse", repeat="infinite") # vida bajatile.animate("pulse", repeat="infinite", duration=1400) # la definitiva, cuando está listaEnfriamientos
Sección titulada «Enfriamientos»Pulsa del 1 al 6: pz.every descuenta los enfriamientos y la capa oscura se encoge con ellos.
def subscriptions(self): return [pz.every(0.1, self.tick, key="cooldowns")] if self.cooldowns else []Prueba esto
Sección titulada «Prueba esto»- Cambia el
accentdel radar a"#f472b6"y su variablesweepa1.0. - Abre
examples/showcase/shaders/nebula.pzshaden Pyzed Design y cambia los colores de su nodo Mix. - Haz que el enfriamiento de cada habilidad dure el doble.
Código completo
Sección titulada «Código completo»"""A game HUD: health and mana bars, a radar minimap, an ability bar and a quest tracker.
python examples/showcase/hud.py
- The space background and the minimap's radar sweep are shader graphs made of nodes in Pyzed Design (``shaders/nebula.pzshad`` and ``shaders/radar.pzshad``): ``pz.shader`` runs them on the GPU for every pixel, every frame.- Panels are ``glass=True`` (a tinted panel with a real backdrop blur), the ready ability and the level-up banner use ``bloom``, low health pulses with ``animate("glow_pulse")``.- Press 1 to 6 (or click a slot) to cast an ability; it goes on cooldown."""
from pathlib import Path
import pyzed as pz
SHADERS = Path(__file__).parent / "shaders"
ABILITIES = ( ("sword", "Slash", 2.0), ("flame", "Fireball", 6.0), ("shield", "Ward", 10.0), ("zap", "Chain bolt", 8.0), ("wind", "Dash", 4.0), ("sparkles", "Ultimate", 30.0),)QUESTS = ( ("Reach the Sunken Gate", True), ("Collect 3 star shards", False), ("Defeat the Warden", False),)GOLD = "#fbbf24"CYAN = "#22d3ee"
class Hud(pz.App): title = "Game HUD" size = (1280, 720) theme = pz.themes.ONE_DARK
def __init__(self): self.health = 0.28 self.mana = 0.72 self.cooldowns = {1: 3.0} # slot -> seconds left self.casts = 0
def subscriptions(self): return [pz.every(0.1, self.tick, key="cooldowns")] if self.cooldowns else []
def tick(self): self.cooldowns = {slot: left - 0.1 for slot, left in self.cooldowns.items() if left > 0.1}
def cast(self, slot): if slot in self.cooldowns: return self.cooldowns[slot] = ABILITIES[slot][2] self.mana = max(0.0, self.mana - 0.08) self.casts += 1
def on_key(self, combo): if combo in ("1", "2", "3", "4", "5", "6"): self.cast(int(combo) - 1) return True
# -- pieces ---------------------------------------------------------------------
def bar(self, value, colors, glow, width=260, height=14, label=None): fill = pz.container(None, width=max(height, width * value), height=height).style( background=pz.Gradient(colors, angle=90), radius=height / 2, glow=(glow, 10)) track = pz.container(fill, width=width, height=height).style( background="#ffffff14", radius=height / 2, border=(1, "#ffffff22")) if label is None: return track return pz.stack([track, pz.container(pz.text(label, size=10, weight="bold", color="#ffffffdd"), width=width, height=height, align_x="center", align_y="center")])
def player(self): portrait = pz.container(pz.icon("swords", size=34, color="#ffffff"), width=72, height=72, align_x="center", align_y="center").style( background=pz.Gradient(["#7c3aed", "#db2777"], angle=135), radius=36, border=(2, "#ffffffaa"), bloom=("#a855f7", 0.5)) health = self.bar(self.health, ["#991b1b", "#ef4444"], "#ef4444", label=f"{round(self.health * 1840)} / 1840") if self.health < 0.3: health = health.animate("glow_pulse", repeat="infinite") stats = pz.column([ pz.row([pz.text("Aria", size=18, weight="bold", color="#ffffff"), pz.text("Lv 27 · Stormblade", size=12, color="#ffffffaa")], spacing=8, align="end"), health, self.bar(self.mana, ["#1d4ed8", "#38bdf8"], "#38bdf8", label=f"{round(self.mana * 960)} / 960"), self.bar(0.64, ["#b45309", GOLD], GOLD, height=5), ], spacing=6) return pz.row([portrait, stats], spacing=14, align="center").style( glass={"tint": "#0f172a", "strength": 0.35, "blur": 18}, radius=16, padding=14)
def minimap(self): radar = pz.shader(SHADERS / "radar.pzshad", uniforms={"accent": "#34d399"}, width=176, height=176) blips = pz.stack([ pz.container(pz.container(None, width=8, height=8).style(background="#f87171", radius=4, bloom=("#f87171", 1.0)), width=176, height=176, padding=(46, 0, 0, 120)), pz.container(pz.container(None, width=8, height=8).style(background=GOLD, radius=4, bloom=(GOLD, 1.0)), width=176, height=176, padding=(118, 0, 0, 52)), pz.container(pz.icon("navigation", size=16, color="#ffffff"), width=176, height=176, align_x="center", align_y="center"), ]) dial = pz.stack([radar, blips], width=176, height=176).style( radius=88, glass={"tint": "#022c22", "strength": 0.25, "blur": 14}, clip=True) return pz.column([ dial, pz.text("Sunken Coast · 412, -88", size=11, color="#ffffffaa"), ], spacing=6, align="center")
def objective(self): return pz.row([ pz.icon("compass", size=16, color=GOLD), pz.text("Reach the Sunken Gate", size=14, weight="semibold", color="#ffffff"), pz.text("120 m", size=12, color="#ffffffaa"), ], spacing=10, align="center").style(glass={"tint": "#0f172a", "strength": 0.3}, radius=20, padding=(8, 16)).animate("fade_in_down")
def quests(self): rows = [pz.text("QUESTS", size=11, weight="bold", color=GOLD)] for name, done in QUESTS: rows.append(pz.row([ pz.icon("circle-check" if done else "circle", size=14, color="#34d399" if done else "#ffffff88"), pz.text(name, size=13, color="#ffffff88" if done else "#ffffff"), ], spacing=8, align="center")) return pz.column(rows, spacing=8).style(glass={"tint": "#0f172a", "strength": 0.3}, radius=14, padding=14, width=230)
def slot(self, index): icon, name, cooldown = ABILITIES[index] left = self.cooldowns.get(index) ready = left is None face = pz.container(pz.icon(icon, size=28, color="#ffffff" if ready else "#ffffff55"), width=64, height=64, align_x="center", align_y="center") layers = [face] if not ready: shade = 64 * left / cooldown layers.append(pz.container(pz.container(None, width=64, height=shade).style(background="#000000aa"), width=64, height=64, align_y="end")) layers.append(pz.container(pz.text(f"{left:.0f}" if left >= 1 else f"{left:.1f}", size=18, weight="bold", color="#ffffff"), width=64, height=64, align_x="center", align_y="center")) layers.append(pz.container(pz.text(str(index + 1), size=10, weight="bold", color="#ffffffcc"), width=64, height=64, padding=(3, 6), align_x="end")) style = dict(radius=12, clip=True, border=(1, "#ffffff33")) if ready and index == 5: style.update(background=pz.Gradient(["#7c2d12", "#ea580c"], angle=135), bloom=(GOLD, 0.9), border=(2, GOLD)) elif ready: style.update(background=pz.Gradient(["#1e293b", "#334155"], angle=135)) else: style.update(background="#0f172a") tile = pz.stack(layers, width=64, height=64).style(**style) tile = pz.mouse_area(tile, on_press=lambda: self.cast(index)) tile = tile.animate("shrink", on="press") if ready and index == 5: tile = tile.animate("pulse", repeat="infinite", duration=1400) return pz.column([tile, pz.text(name, size=10, color="#ffffffaa")], spacing=4, align="center")
def abilities(self): return pz.row([self.slot(i) for i in range(len(ABILITIES))], spacing=10).style( glass={"tint": "#020617", "strength": 0.4, "blur": 16}, radius=18, padding=(12, 16, 8, 16))
def log(self): lines = ( ("You hit Tide Wraith for 312", "#ffffff"), ("Critical! Chain bolt 1,204", GOLD), ("Tide Wraith hits you for 188", "#f87171"), ("Ward absorbed 140 damage", CYAN), ) return pz.column([pz.text(t, size=12, color=c, font="mono") for t, c in lines], spacing=4).style( glass={"tint": "#020617", "strength": 0.3}, radius=12, padding=12, width=300)
def level_up(self): return pz.column([ pz.text("LEVEL UP", size=40, weight="black", color="#fff7d6").style(bloom=(GOLD, 1.0)), pz.text("Stormblade · Level 27", size=14, color="#fde68a"), ], spacing=2, align="center").animate("pop_in").animate("float", repeat="infinite")
def view(self): def at(widget, x, y, pad): return pz.container(widget, width="fill", height="fill", align_x=x, align_y=y, padding=pad)
return pz.stack([ pz.shader(SHADERS / "nebula.pzshad", width="fill", height="fill", fallback=pz.Gradient(["#05040f", "#2a0c46", "#0b3a4a"], angle=135)), at(self.player(), "start", "start", 20), at(self.objective(), "center", "start", 24), at(self.minimap(), "end", "start", 20), at(self.quests(), "end", "center", 20), at(self.level_up(), "center", "center", (0, 0, 120, 0)), at(self.log(), "start", "end", 20), at(self.abilities(), "center", "end", 20), ])
if __name__ == "__main__": pz.run(Hud)