Voltar para o Blog
Quest Log

Barra de Vida na Unity: Slider, Image Fill e Inimigos

Barra de vida vermelha com um trecho amarelo descendo atrás dela, flutuando sobre um inimigo num cenário de jogo

Unity barra de vida com Slider ou Image Fill: HUD do jogador, barra em cima do inimigo, barra de dano atrasada e código C# por evento que serve em 2D e 3D.

Ferramenta:UnityC#

Barra de Vida na Unity: Slider, Image Fill e Inimigos

Para fazer uma unity barra de vida você precisa de duas coisas separadas: um script que guarda a vida e avisa quando ela muda, e um elemento de UI que mostra essa vida como fração entre 0 e 1. A UI pode ser um Slider ou uma Image com Image Type em Filled, e o valor que você passa é sempre vidaAtual / vidaMaxima. Feito assim, a mesma barra serve para o jogador no HUD e para o inimigo no mundo.

Aqui você monta os dois formatos, liga a barra por evento (sem a UI perguntar a vida todo frame), coloca uma barra em cima do inimigo em World Space e faz o efeito de barra de dano atrasada, aquele trecho de outra cor que desce depois do golpe. O código está no Unity 6.3 LTS e funciona em 2D e 3D.

Barra de vida na Unity: Slider ou Image Fill

Os dois funcionam. A diferença está no que vem de brinde.

Slider (GameObject > UI > Slider) já vem com fundo, área de preenchimento e um Handle para arrastar. Para virar barra de vida você precisa: apagar o Handle, desmarcar Interactable, colocar Navigation em None (senão o controle pode selecionar a barra) e definir Min Value 0 e Max Value 1. Ele tem uma chatice conhecida: em valor zero, dependendo das margens do Fill Area, sobra um pedacinho de cor visível.

Image Filled é uma Image comum com Image Type em Filled. Você ganha os campos Fill Method (Horizontal, Vertical, Radial 90, Radial 180, Radial 360), Fill Origin (de que lado começa) e Fill Amount (0 a 1). Zero é zero, sem sobra, e o modo Radial dá barra circular de graça. É o que eu uso para vida.

Um detalhe que trava quem tenta pela primeira vez: o campo Image Type só aparece quando a Image tem um Source Image. Sem sprite, você não acha a opção Filled. Use qualquer sprite branco, por exemplo o quadrado criado em Assets > Create > 2D > Sprites > Square, e pinte pela cor da Image.

Montando a barra do jogador no HUD

Monte esta hierarquia dentro do Canvas do HUD:

  1. BarraVida (objeto vazio com RectTransform), ancorado no canto superior esquerdo, 300 x 28 pixels.
  2. Filho Fundo: Image, cor cinza escura, esticada no pai (Anchor Presets com Alt + stretch total).
  3. Filho Atrasada: Image, sprite branco, cor amarela, Image Type: Filled, Fill Method: Horizontal, Fill Origin: Left, esticada no pai.
  4. Filho Preenchimento: igual ao anterior, mas vermelho. Ele fica abaixo de Atrasada na Hierarchy, para ser desenhado por cima.
  5. Opcional: um texto TextMeshPro com "100/100" por cima de tudo.

A ordem dos filhos importa: no uGUI, quem está mais embaixo na Hierarchy é desenhado por último, logo por cima. Desmarque Raycast Target em todas essas Images; barra de vida não precisa receber clique e pode roubar o clique de botões próximos.

Script de vida com evento de dano

O script de vida não sabe que a barra existe. Ele só anuncia "minha vida mudou". Quem quiser ouvir, ouve.

Arquivo Vida.cs:

using System;
using UnityEngine;

public class Vida : MonoBehaviour
{
    [SerializeField] private int vidaMaxima = 100;

    private int vidaAtual;

    public event Action<int, int> VidaMudou;
    public event Action Morreu;

    public int VidaAtual => vidaAtual;
    public int VidaMaxima => vidaMaxima;

    private void Awake()
    {
        vidaAtual = vidaMaxima;
    }

    public void ReceberDano(int quantidade)
    {
        if (vidaAtual <= 0)
        {
            return;
        }

        vidaAtual = Mathf.Max(vidaAtual - quantidade, 0);
        VidaMudou?.Invoke(vidaAtual, vidaMaxima);

        if (vidaAtual == 0)
        {
            Morreu?.Invoke();
        }
    }

    public void Curar(int quantidade)
    {
        if (vidaAtual <= 0)
        {
            return;
        }

        vidaAtual = Mathf.Min(vidaAtual + quantidade, vidaMaxima);
        VidaMudou?.Invoke(vidaAtual, vidaMaxima);
    }
}

Por que evento e não a barra lendo vida.VidaAtual no Update? Porque a vida muda poucas vezes por minuto e a barra não precisa trabalhar nos outros milhares de frames. E porque o mesmo evento alimenta outras coisas: som de dano, tremida de câmera, flash vermelho na tela. Se você quer o sistema completo, com invencibilidade depois do golpe e morte, o post de sistema de vida e dano na Unity aprofunda esse script. A lógica é a mesma que o sistema de vida e dano no Godot usa com sinais, então dá para comparar as duas engines lado a lado.

Barra de dano atrasada que desce suave

Agora a barra. Ela escuta o evento, pula o vermelho direto para o valor novo e deixa o amarelo descer devagar depois de uma pausa curta.

Arquivo BarraDeVida.cs:

using UnityEngine;
using UnityEngine.UI;

public class BarraDeVida : MonoBehaviour
{
    [SerializeField] private Vida vida;
    [SerializeField] private Image preenchimento;
    [SerializeField] private Image preenchimentoAtrasado;
    [SerializeField] private float esperaAntesDeDescer = 0.4f;
    [SerializeField] private float velocidadeDaAtrasada = 0.8f;

    private float fracaoAlvo = 1f;
    private float tempoAteDescer;

    private void OnEnable()
    {
        vida.VidaMudou += AoMudarVida;
    }

    private void OnDisable()
    {
        vida.VidaMudou -= AoMudarVida;
    }

    private void Start()
    {
        fracaoAlvo = (float)vida.VidaAtual / vida.VidaMaxima;
        preenchimento.fillAmount = fracaoAlvo;
        preenchimentoAtrasado.fillAmount = fracaoAlvo;
    }

    private void AoMudarVida(int atual, int maxima)
    {
        float novaFracao = (float)atual / maxima;

        if (novaFracao < fracaoAlvo)
        {
            tempoAteDescer = esperaAntesDeDescer;
        }
        else
        {
            preenchimentoAtrasado.fillAmount = novaFracao;
        }

        fracaoAlvo = novaFracao;
        preenchimento.fillAmount = fracaoAlvo;
    }

    private void Update()
    {
        if (preenchimentoAtrasado.fillAmount <= fracaoAlvo)
        {
            return;
        }

        if (tempoAteDescer > 0f)
        {
            tempoAteDescer -= Time.deltaTime;
            return;
        }

        preenchimentoAtrasado.fillAmount = Mathf.MoveTowards(
            preenchimentoAtrasado.fillAmount,
            fracaoAlvo,
            velocidadeDaAtrasada * Time.deltaTime);
    }
}

Coloque o BarraDeVida no objeto BarraVida, arraste o jogador (com o Vida) e as duas Images. Por que Mathf.MoveTowards e não Mathf.Lerp? O MoveTowards desce a uma velocidade constante (0.8 da barra por segundo, no exemplo), então um golpe de 10% e um de 50% demoram tempos proporcionais, e o jogador lê o tamanho do dano. O Lerp com Time.deltaTime desacelera no fim e nunca chega exatamente ao alvo, o que deixa uma lasquinha amarela parada. A espera de 0.4 s antes de descer é o que dá a sensação de impacto: o jogador vê o tamanho do estrago antes dele sumir. Esse tipo de detalhe é o que o post de game feel e juice chama de feedback, e faz o golpe parecer mais pesado sem mudar um número do combate.

Na cura, a barra atrasada pula junto com a principal. Se preferir, inverta a lógica para cura: amarelo pula, vermelho sobe devagar.

Até aqui são dois scripts e meia dúzia de Images. O trabalho de verdade aparece quando isso entra num jogo: vinte inimigos com barra, o chefe com barra grande no topo, o jogador com HUD, tudo atualizando por evento sem vazar referência quando o inimigo morre. É juntar as peças num projeto inteiro, sozinho, que costuma travar.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Barra de vida em cima do inimigo (World Space)

Para o inimigo, a barra fica no mundo, presa a ele.

  1. Clique com o botão direito no prefab do inimigo e crie UI > Canvas como filho.
  2. No Canvas, mude Render Mode para World Space.
  3. Ajuste o RectTransform para 100 x 12 e a Scale para 0.01 nos três eixos. Assim a barra tem 1 unidade de largura no mundo, que é a escala de um sprite de 100 pixels por unidade ou de um personagem 3D de 1 metro.
  4. Posicione o Canvas acima da cabeça (Pos Y em torno de 1.2 a 2, dependendo do modelo).
  5. Copie para dentro dele a mesma hierarquia Fundo / Atrasada / Preenchimento e adicione o BarraDeVida apontando para o Vida do próprio inimigo.

Como o script é o mesmo, não há código novo para o inimigo. Num jogo com muitos inimigos, vale esconder a barra enquanto a vida está cheia (desligue o componente Canvas com canvas.enabled = false no Start e ligue no primeiro dano; não desative o GameObject, senão o OnDisable do BarraDeVida cancela a inscrição e a barra para de ouvir o evento) para a tela não virar um mar de barras.

Funciona em 2D e 3D: o que muda

Em 3D, a barra precisa olhar para a câmera, ou o jogador vê de lado (uma linha fina) quando o inimigo gira.

Arquivo OlharParaCamera.cs:

using UnityEngine;

public class OlharParaCamera : MonoBehaviour
{
    private Camera cameraPrincipal;

    private void Start()
    {
        cameraPrincipal = Camera.main;
    }

    private void LateUpdate()
    {
        if (cameraPrincipal == null)
        {
            return;
        }

        transform.rotation = cameraPrincipal.transform.rotation;
    }
}

Coloque no Canvas da barra. O LateUpdate roda depois que o inimigo e a câmera já se moveram no frame, então a barra não treme. Copiar a rotação da câmera deixa a barra paralela à tela, que é o que você quer em UI.

Em 2D, a câmera ortográfica olha sempre na mesma direção, então não precisa do script. O cuidado é outro: o Canvas em World Space precisa aparecer na frente dos sprites. No Canvas, ajuste Sorting Layer e Order in Layer para ficar acima da camada dos personagens.

Erros comuns: barra invertida, olhando para trás e não atualizando

Barra que esvazia para o lado errado. No Image Filled, troque o Fill Origin de Left para Right. No Slider, é o campo Direction (Left To Right ou Right To Left).

Barra olhando para trás ou espelhada. Você usou transform.LookAt(camera.transform). O LookAt aponta o eixo Z do Canvas para a câmera, e a frente do Canvas é o lado contrário, então você vê a barra de costas. Use a cópia de rotação do script acima.

Barra do inimigo 2D invertendo junto com ele. Se o inimigo vira trocando o Scale X para -1, todo filho vira também, e a barra esvazia para o lado oposto. Vire o inimigo pelo Flip X do SpriteRenderer, ou deixe a barra fora do objeto que inverte.

Barra que não atualiza. Três suspeitos: divisão inteira (atual / maxima com dois int dá 0 ou 1, por isso o (float) no código), o campo vida do BarraDeVida apontando para outro objeto (um prefab no Project em vez da instância da cena) ou o Image Type que voltou para Simple, que ignora o Fill Amount.

Próximo passo: transformar a barra em game feel

Com a barra funcionando, pratique variações no mesmo código: barra radial para cooldown de habilidade (Fill Method Radial 360), barra de escudo sobreposta à de vida e um número de dano saltando do inimigo a cada golpe. Para mostrar o número da vida ao lado da barra, use um TMP_Text e o SetText com formato, como SetText("{0}/{1}", atual, maxima), dentro do mesmo AoMudarVida: o texto só muda quando a vida muda e não gera lixo de memória.

Perguntas frequentes

É melhor fazer barra de vida com Slider ou com Image Fill na Unity?

Image com Image Type Filled é mais simples e mais limpa para barra de vida: não tem handle, não reage a clique e chega a zero sem sobrar pedaço. O Slider serve quando você já tem um prefab pronto ou quer a barra arrastável num menu de opções.

Por que minha barra de vida fica cheia ou vazia e não muda no meio?

Quase sempre é divisão inteira. vidaAtual / vidaMaxima com dois int dá 0 ou 1. Converta para float antes de dividir: (float)vidaAtual / vidaMaxima.

Como fazer a barra de vida do inimigo sempre olhar para a câmera em 3D?

Use um Canvas em World Space como filho do inimigo e, no LateUpdate, copie a rotação da câmera para o Canvas: transform.rotation = camera.transform.rotation. Usar LookAt na câmera deixa a barra espelhada.

Como fazer a barra de vida descer devagar depois do dano?

Use duas Images sobrepostas. A da frente pula direto para o valor novo e a de trás, de outra cor, espera uma fração de segundo e desce com Mathf.MoveTowards até alcançar a da frente.