Skip to content

Game HUD

A complete heads-up display over an animated space nebula. Everything is ordinary Pyzed widgets: the nebula and the radar sweep are shader graphs (.pzshad files made of nodes in Pyzed Design), the panels are glass, and the ready ability and the level-up banner use bloom.

Game HUD

Windows, macOS and Linux
uv run --with pyzed-ui python examples/showcase/hud.py

Press 1 to 6 (or click a slot) to cast an ability.

The nebula is a graph of domain-warped noise, redrawn every frame by the GPU. The radar sweep is a second graph inside a round glass panel; its accent variable sets the color from 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)

See both graphs in Shaders and effects.

Each panel blurs what is behind it and tints it.

pz.row([...]).style(glass={"tint": "#0f172a", "strength": 0.35, "blur": 18}, radius=16)

Low health pulses, the ultimate ability blooms and pulses when it is ready.

health.animate("glow_pulse", repeat="infinite") # low health
tile.animate("pulse", repeat="infinite", duration=1400) # the ultimate, when ready

Press 1 to 6: pz.every counts the cooldowns down and the overlay shrinks with them.

def subscriptions(self):
return [pz.every(0.1, self.tick, key="cooldowns")] if self.cooldowns else []
  • Change the radar’s accent to "#f472b6" and its sweep variable to 1.0.
  • Open examples/showcase/shaders/nebula.pzshad in Pyzed Design and change the colors of its Mix node.
  • Make every ability’s cooldown twice as long.
examples/showcase/hud.py
"""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)