Pular para o conteúdo
AstraDOCS

HUD de corações: do editor ao dano e cura

Monte três corações com Image, ligue à vida do jogador e confira dano, cura e zero vida.

Sim, pela composição Canvas UI + Image + Behavior C#. Image desenha o ícone e permite mudar Visible. A regra de vida é implementada no script PlayerHealth do exemplo. Não existe um componente pronto de “HUD de corações” ou binding automático de vida no snapshot. Este tutorial usa três corações inteiros; meio coração/preenchimento radial exigem outra composição, e Image não tem fillAmount.

Jogador HUD (objeto da cena)
└─ PlayerHealth ├─ Canvas UI → UI/hud.aeui
└─ HeartHud → Player = Jogador
UI/hud.aeui (documento)
├─ Hearts (Panel, sem fundo)
│ ├─ Empty1 (Image vazio) ← fica visível
│ ├─ Heart1 (Image cheio) ← script liga/desliga
│ ├─ Empty2 (Image vazio)
│ ├─ Heart2 (Image cheio)
│ ├─ Empty3 (Image vazio)
│ └─ Heart3 (Image cheio)
├─ Status (Text)
├─ Damage (Button)
└─ Heal (Button)

Os seis Images ficam sobrepostos em pares. Ao esconder Heart2, Empty2 continua desenhado na mesma posição. Use Panel com layout manual aqui; esconder filhos diretos de HBox pode reorganizar a fileira. A ordem é relevante: vazio primeiro, cheio depois.

Coração cheioCoração cheioCoração vazio
Vida 2/3: ilustração com os recursos do tutorial. A execução real ocorre na Astra em Play.

Adicione Images/heart-full.png e Images/heart-empty.png dentro do projeto. Use duas imagens 64 × 64 com alpha; ambas precisam existir e ser legíveis. Baixe o pacote do tutorial: inclui UI/hud.aeui, PNGs, scripts e instruções, para integrar num projeto existente. Abra e salve o .aeui pela área Interface para registrá-lo como recurso, ou siga a autoria manual abaixo. As duas imagens já aparecem no documento autoral, então o atlas conhece os dois recursos antes de esconder uma camada.

Siga o caminho de autoria e salve UI/hud.aeui. Crie Panel e renomeie para Hearts. Selecionar Panel antes de criar Image faz os filhos pertencerem a ele. Todos usam âncoras inicial e final (0,0).

Elemento Pai Bordas: esquerda, topo, direita, base Recurso / texto
Hearts, Panel raiz 24,24,184,72 Desenhar fundo desligado
Empty1, Image Hearts 0,0,48,48 Images/heart-empty.png
Heart1, Image Hearts 0,0,48,48 Images/heart-full.png
Empty2, Image Hearts 56,0,104,48 Images/heart-empty.png
Heart2, Image Hearts 56,0,104,48 Images/heart-full.png
Empty3, Image Hearts 112,0,160,48 Images/heart-empty.png
Heart3, Image Hearts 112,0,160,48 Images/heart-full.png
Status, Text raiz 24,80,320,116 Vida: 3/3
Damage, Button raiz 24,124,124,168 Dano
Heal, Button raiz 136,124,236,168 Cura

Para os Images, use Ajuste = Conter, Tinta = branco, Visível = ligado, Clicável = desligado e sem animação. Panel Hearts deve ter fundo transparente. Damage e Heal usam ação Notificar; não os configure para alterar Value do Image. Após criar os filhos, selecione a raiz/limpe a seleção antes de criar Status e os botões. Salve UI.

  1. Crie HUD, adicione Canvas UI, atribua UI/hud.aeui em Documento UI, use Tela e Habilitado.
  2. Crie ou escolha Jogador. Ele pode ser só um objeto vazio para a demonstração por botão; física não é necessária nessa etapa.
  3. Adicione os scripts abaixo na área de código, compile e anexe PlayerHealth a Jogador e HeartHud a HUD.
  4. No Inspector do HeartHud, campo Player, escolha o objeto Jogador. Não escolha um Image da árvore UI: o campo é uma referência a objeto da cena.
  5. Salve a cena e a UI. Receptor/câmera de entrada do Canvas podem ficar sem atribuição nesta demonstração visual.

Baixar PlayerHealth.cs

using System;
using Astra;
[ComponentId("docs.player-health")]
public sealed class PlayerHealth : Behavior
{
public const int Capacity = 3;
public int Current { get; private set; }
public bool IsDead => Current == 0;
public override void Awake() => Current = Capacity;
public void TakeDamage(int amount)
{
Current = Math.Max(0, Current - Math.Clamp(amount, 0, Capacity));
}
public void Heal(int amount)
{
Current = Math.Min(Capacity, Current + Math.Clamp(amount, 0, Capacity));
}
}

Awake começa com três corações. TakeDamage e Heal aceitam quantidades não negativas, limitadas à capacidade. Zero vida apenas muda o estado IsDead; este exemplo não inventa morte, respawn, animação ou persistência de save.

Baixar HeartHud.cs

using System;
using Astra;
[ComponentId("docs.heart-hud")]
public sealed class HeartHud : Behavior
{
[PropertyId("player")]
public ObjectReference Player;
private GuiAccess hud = null!;
private PlayerHealth health = null!;
private readonly GuiElement[] hearts = new GuiElement[PlayerHealth.Capacity];
private GuiElement status;
private int lastHealth = -1;
public override void Start()
{
var player = Resolve(Player)
?? throw new InvalidOperationException("Atribua Jogador ao campo Player do HeartHud.");
health = player.GetBehavior<PlayerHealth>()
?? throw new InvalidOperationException("Jogador precisa de PlayerHealth.");
hud = Gui.ForCanvas(Object);
for (int i = 0; i < hearts.Length; ++i)
{
hearts[i] = hud.Find($"Heart{i + 1}");
if (hearts[i].Kind != GuiKind.Image)
throw new InvalidOperationException($"Heart{i + 1} precisa ser Image.");
}
status = hud.Find("Status");
hud.Find("Damage");
hud.Find("Heal");
Refresh();
}
public override void Update(float deltaTime)
{
// Este script é o consumidor da fila deste Canvas.
while (hud.Poll(out var message))
{
if (message.Kind != GuiEventKind.Click) continue;
if (message.Element.Name == "Damage") health.TakeDamage(1);
else if (message.Element.Name == "Heal") health.Heal(1);
}
if (lastHealth != health.Current) Refresh();
}
private void Refresh()
{
lastHealth = health.Current;
for (int i = 0; i < hearts.Length; ++i)
hearts[i].Visible = i < lastHealth;
status.Text = $"Vida: {lastHealth}/{PlayerHealth.Capacity}"
+ (health.IsDead ? " | Sem vida" : "");
}
}

Gui.ForCanvas(Object) escolhe o Canvas do objeto HUD. Start resolve nomes e conserva handles somente desta execução. O script verifica os tipos dos corações e falha com mensagem quando Player/PlayerHealth estão ausentes. Update consome os botões e só escreve as imagens quando a vida muda. Não precisa procurar Heart1–3 a cada frame.

Outro Behavior pode resolver o jogador e chamar player.GetBehavior<PlayerHealth>()?.TakeDamage(1) no momento real de um ataque. Para uma zona de dano, o arquivo abaixo usa TriggerEnter, uma vez por entrada, não TriggerStay a cada passo:

using Astra;
[ComponentId("docs.heart-damage-trigger")]
public sealed class HeartDamageTrigger : Behavior
{
public override void TriggerEnter(ObjectReference other)
{
Resolve(other)?.GetBehavior<PlayerHealth>()?.TakeDamage(1);
}
}

Baixar HeartDamageTrigger.cs. Anexe à zona com corpo/colisor 3D em modo sensor. O outro objeto precisa participar da física 3D e carregar PlayerHealth. Consulte Corpo físico, Colisor 3D e eventos físicos. Eventos 2D têm contrato próprio; não substitua assinaturas por suposição. O script não define cooldown nem identifica filhos sem PlayerHealth: coloque o Behavior no objeto resolvido pelo evento ou adapte a busca explicitamente.

Ação em Play Vida esperada HUD esperado
Iniciar 3 três cheios, texto Vida: 3/3
Dano 2 dois cheios e um vazio
Dano duas vezes 0 três vazios, texto Sem vida
Dano em zero 0 continua zero
Cura 1 um cheio e dois vazios
Cura até o máximo 3 não ultrapassa três
Parar e reabrir Play 3 reinicia, documento autoral preservado

Interagir não executa esses scripts. Use Play para validar a ligação com a vida. Se a fonte recarregar durante Play, resolva novos handles ou reinicie.

Os três scripts deste tutorial compilaram sem erros contra o SDK do APK publicado. O documento do pacote foi gravado e relido pelo serializador nativo: formato AEUI 5, dez elementos. Isso verifica contratos e formato; o roteiro acima ainda precisa de aceite no Play e no aparelho. Consulte o manifesto de exemplos para a evidência de compilação.

Os corações usam 160 × 48 px e margem 24 px no viewport Tela. Eles conservam tamanho e posição no canto superior esquerdo, sem assumir escala por resolução de referência. Confira landscape pequeno e tablet, notch/área segura, contraste e espaço da cena. Safe area automática e escala responsiva completa continuam em R5. Para fileira flexível, use HBox com slots Panel de tamanho preferido 48 × 48 e as duas Images dentro de cada slot.

Meios corações podem ser autorados com imagens adicionais e regra de meio ponto; nine-slice, fill radial, binding automático, timeline e templates completos continuam no roadmap. Vida persistente entre sessões pede SaveStore e regra explícita de save/load; não é consequência de salvar UI. Para erros, veja diagnóstico de Image e HUD.