hud-system
Skill870 starsChanged 22 days agoMITView on GitHub
Use when building in-game HUDs — health bars, score displays, minimap, notifications, and damage numbers
In this repository62 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- authoring-godot-prompter-skillsSkill
- releasing-godot-prompterSkill
- 2d-essentialsSkill
- 3d-essentialsSkill
- ability-systemSkill
- addon-developmentSkill
- ai-navigationSkill
- animation-systemSkill
- assets-pipelineSkill
- audio-systemSkill
- beehaveSkill
- camera-systemSkill
- component-systemSkill
- csharp-godotSkill
- csharp-signalsSkill
- dedicated-serverSkill
- dependency-injectionSkill
- dialogue-managerSkill
- dialogue-systemSkill
- event-busSkill
- export-pipelineSkill
- gdextensionSkill
- gdscript-advancedSkill
- gdscript-patternsSkill
- godot-brainstormingSkill
- godot-code-reviewSkill
- godot-debuggingSkill
- godot-grillSkill
- godot-mentorSkill
- godot-optimizationSkill
- godot-project-setupSkill
- godot-testingSkill
- godot-uiSkill
- input-handlingSkill
- inventory-systemSkill
- limboaiSkill
- localizationSkill
- math-essentialsSkill
- mobile-developmentSkill
- multiplayer-basicsSkill
- multiplayer-syncSkill
- multithreadingSkill
- particles-vfxSkill
- phantom-cameraSkill
- physics-systemSkill
- player-controllerSkill
- popochiuSkill
- procedural-generationSkill
- rendering-pipelineSkill
- resource-patternSkill
- responsive-uiSkill
- save-loadSkill
- scene-organizationSkill
- shader-basicsSkill
- state-machineSkill
- tween-animationSkill
- using-godot-prompterSkill
- web-exportSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 1 is distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
All examples target Godot 4.3+ with no deprecated APIs.
- Very specific: line 47
Keep all HUD scenes under a single CanvasLayer.
- Very specific: line 48
Use layer = 1 for the main HUD.
- Very specific: line 169
Tip: If you use TextureProgressBar, set fill_mode to FILL_LEFT_TO_RIGHT and assign your bar texture to texture_progress.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
HUD Systems in Godot 4.3+
All examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.
Related skills: godot-ui for Control node layout and themes, component-system for HealthComponent integration, event-bus for score/notification signals, inventory-system for inventory UI patterns, 2d-essentials for CanvasLayer setup and draw order, ability-system for cooldown bar and resource bar binding patterns.
1. HUD Architecture
Why CanvasLayer
A CanvasLayer renders its children with its own transform, independent of the Camera2D (unless follow_viewport_enabled is on); a Camera3D never moves 2D nodes. Without it, HUD nodes on the scene root's default canvas (layer 0) move with a Camera2D when you pan or zoom, because the camera sets that canvas's transform. Wrapping all HUD nodes in a CanvasLayer keeps the HUD in place; layer ≥ 1 (the default is 1) draws it above the world.
Scene Tree
World (Node2D / Node3D)
├── TileMapLayer ← game world
├── Player (CharacterBody2D)
│ ├── Camera2D
│ ├── HealthComponent
│ └── HurtboxComponent
├── Enemies
└── HUD (CanvasLayer — layer: 1)
├── MarginContainer (anchor: Full Rect — provides edge padding)
│ ├── TopBar (HBoxContainer)
│ │ ├── HealthBarPanel (PanelContainer)
│ │ │ └── HealthBar (TextureProgressBar or ProgressBar)
│ │ └── ScoreLabel (Label)
│ └── BottomBar (HBoxContainer)
│ └── InteractionPrompt (Label — hidden by default)
├── DamageNumbersLayer (Node2D — world-space spawning point)
├── MinimapContainer (SubViewportContainer)
│ └── MinimapViewport (SubViewport)
│ ├── MinimapCamera (Camera2D)
│ └── MinimapWorld (mirrors or references world nodes)
└── NotificationStack (VBoxContainer — anchored top-right)Key rules:
- Keep all HUD scenes under a single
CanvasLayer. Do not mix HUD nodes into the game world tree. - Use
layer = 1for the main HUD. Use higher values (e.g.10) for overlays or pause menus that must appear above the HUD. - Damage numbers are an exception — they can live in a
Node2Dchild of theCanvasLayerand useget_viewport().get_canvas_transform()to convert world positions to screen positions.
2. Health Bar
ProgressBar vs TextureProgressBar
| Node | When to use |
ProgressBar | Prototyping, plain-colour bars |
TextureProgressBar | Pixel-art or stylised bars using sprite sheets |
Both expose min_value, max_value, and value. Set step = 0 so tweening produces a smooth animation rather than snapping to steps (TextureProgressBar defaults step to 1, ProgressBar to 0.01).
GDScript
## health_bar.gd — attach to a ProgressBar or TextureProgressBar
class_name HealthBar
extends ProgressBar
## Reference to the HealthComponent this bar tracks.
## Assign in the Inspector or connect programmatically from the HUD root.
@export var health_component: HealthComponent
## Duration (seconds) for the smooth tween on health change.
@export var tween_duration: float = 0.25
var _tween: Tween
func _ready() -> void:
step = 0.0 # allow fractional values for smooth animation
if health_component:
_connect_component(health_component)
## Call this if the HealthComponent is not available at _ready time
## (e.g. the player spawns after the HUD).
func bind(component: HealthComponent) -> void:
if health_component:
health_component.health_changed.disconnect(_on_health_changed)
health_component = component
_connect_component(component)
func _connect_component(component: HealthComponent) -> void:
max_value = component.max_health
value = component.current_health
component.health_changed.connect(_on_health_changed)
func _on_health_changed(current: int, maximum: int) -> void:
max_value = maximum
_animate_to(current)
func _animate_to(target_value: float) -> void:
if _tween:
_tween.kill()
_tween = create_tween()
_tween.set_ease(Tween.EASE_OUT)
_tween.set_trans(Tween.TRANS_QUAD)
_tween.tween_property(self, "value", target_value, tween_duration)C#
// HealthBar.cs — attach to a ProgressBar or TextureProgressBar
using Godot;
public partial class HealthBar : ProgressBar
{
[Export] public HealthComponent HealthComponent { get; set; }
[Export] public float TweenDuration { get; set; } = 0.25f;
private Tween _tween;
public override void _Ready()
{
Step = 0.0;
if (HealthComponent != null)
ConnectComponent(HealthComponent);
}
/// <summary>Call this when the HealthComponent is not available at _Ready time.</summary>
public void Bind(HealthComponent component)
{
if (HealthComponent != null)
HealthComponent.HealthChanged -= OnHealthChanged;
HealthComponent = component;
ConnectComponent(component);
}
private void ConnectComponent(HealthComponent component)
{
MaxValue = component.MaxHealth;
Value = component.CurrentHealth;
component.HealthChanged += OnHealthChanged;
}
private void OnHealthChanged(int current, int maximum)
{
MaxValue = maximum;
AnimateTo(current);
}
private void AnimateTo(float targetValue)
{
_tween?.Kill();
_tween = CreateTween();
_tween.SetEase(Tween.EaseType.Out);
_tween.SetTrans(Tween.TransitionType.Quad);
_tween.TweenProperty(this, "value", targetValue, TweenDuration);
}
}Tip: If you use TextureProgressBar, set fill_mode to FILL_LEFT_TO_RIGHT and assign your bar texture to texture_progress. The value / max_value ratio drives how much of the texture is revealed.
3. Score / Label Display
GDScript
## score_display.gd — attach to a Label
class_name ScoreDisplay
extends Label
## Duration (seconds) to count from old to new score value.
@export var count_duration: float = 0.4
var _displayed_score: int = 0
var _tween: Tween
func _ready() -> void:
EventBus.score_changed.connect(_on_score_changed)
text = "0"
func _on_score_changed(new_score: int) -> void:
_animate_counter(_displayed_score, new_score)
func _animate_counter(from: int, to: int) -> void:
if _tween:
_tween.kill()
_tween = create_tween()
_tween.set_ease(Tween.EASE_OUT)
_tween.set_trans(Tween.TRANS_QUAD)
# Tween an intermediate float; update the label text each step.
_tween.tween_method(_set_counter_value, float(from), float(to), count_duration)
func _set_counter_value(value: float) -> void:
_displayed_score = int(value)
text = str(_displayed_score)C#
// ScoreDisplay.cs — attach to a Label
using Godot;
public partial class ScoreDisplay : Label
{
[Export] public float CountDuration { get; set; } = 0.4f;
private int _displayedScore = 0;
private Tween _tween;
public override void _Ready()
{
GetNode<EventBus>("/root/EventBus").ScoreChanged += OnScoreChanged;
Text = "0";
}
private void OnScoreChanged(int newScore)
{
AnimateCounter(_displayedScore, newScore);
}
private void AnimateCounter(int from, int to)
{
_tween?.Kill();
_tween = CreateTween();
_tween.SetEase(Tween.EaseType.Out);
_tween.SetTrans(Tween.TransitionType.Quad);
_tween.TweenMethod(
Callable.From<double>(SetCounterValue),
(double)from,
(double)to,
CountDuration
);
}
private void SetCounterValue(double value)
{
_displayedScore = (int)value;
Text = _displayedScore.ToString();
}
}EventBus signals needed:
# autoloads/event_bus.gd
signal score_changed(new_score: int)// EventBus.cs (partial — score signal)
[Signal] public delegate void ScoreChangedEventHandler(int newScore);Emit from wherever points are awarded:
# Inside a collectible or enemy death handler
EventBus.score_changed.emit(GameState.score)// Inside a collectible or enemy death handler
GetNode<EventBus>("/root/EventBus").EmitSignal(EventBus.SignalName.ScoreChanged, GameState.Score);4. Damage Numbers
Floating "−25" labels that rise and fade above the hit point. Pooled in a HUD-side spawner; world position converted to screen via get_viewport().get_canvas_transform(). Optional crit colorization before spawn.
See references/damage-numbers.md for the full GDScript and C# DamageNumber scene + pooled spawner.
5. Notification System
Toast / notification stack — a VBoxContainer anchored top-right with max_visible clamping and queue-driven dismissal. New toasts wait for an old one to expire before showing.
See references/notifications.md for the full GDScript and C# stack with auto-dismiss timers.
6. Minimap Concept
Render a top-down view via a dedicated SubViewport + Camera2D that follows the player. Display the SubViewport texture in a TextureRect inside the HUD. Optional circular mask via ColorRect shader. Set render_target_update_mode = UPDATE_ALWAYS.
See references/minimap.md for the SubViewport setup, MinimapCamera GDScript + C#, and circular-mask shader.
7. Interaction Prompts
Screen-space "Press [E] to interact" prompt — a Label inside the HUD that follows an interactable's screen position each frame. Driven by body_entered / body_exited on the interactable's Area2D. Use InputMap.action_get_events(name) to display the correct key for the player's current binding.
See references/interaction-prompts.md for the full GDScript and C# prompt + Interactable Area2D pair.
8. Checklist
- All HUD nodes are children of a
CanvasLayer(unaffected byCamera2D) withlayer >= 1so they draw above the world stepis set to0.0for smooth tween animation (TextureProgressBarsnaps to whole values by default)- Health bar binds to
HealthComponent.health_changedsignal — does not poll in_process - Tween is killed (
_tween.kill()) before starting a new one so rapid damage does not stack animations - Score counter uses
tween_methodto interpolate the displayed integer — not a jump cut - Damage number positions are converted from world space to screen space using
get_viewport().get_canvas_transform() - Damage number pool size is large enough that labels are not recycled before their tween completes
- Notification stack enforces
max_visibleand re-checks the queue after each dismissal - Toast auto-dismiss uses a
Timernode — notawait get_tree().create_timer() SubViewportfor minimap hasrender_target_update_mode = UPDATE_ALWAYS- Minimap
Camera2Dzoom and theSubViewport'scanvas_cull_maskare configured so only the intended visibility layers are visible - Interaction prompt converts the interactable's world position each frame — not cached at spawn time
InputMap.action_get_events()is used to display the correct key for the player's current binding- HUD nodes that do not need input set
mouse_filter = MOUSE_FILTER_IGNOREto avoid blocking game clicks
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

