4. Salud y Daño

En el Capítulo de Hoy crearemos y añadiremos un “Sistema de Salud” a nuestro “Primer Juego 3D” en Unity 🙂

Para ser más precisos, crearemos un sistema de salud que añadiremos a nuestro personaje el cual permitirá recibir daño de objetos presentes en la escena así como poder curarse mediante otros. Además, esto podrá ser visible mediante el uso de una “barra de vida” en la pantalla de nuestro juego.

Tutorial de Unity Nivel: Principiante – Intermedio.

4.1 Los Preparativos.

Antes de comenzar con los códigos vamos a realizar los siguientes ajustes que permitirán a nuestro “Sistema de Salud” funcionar adecuadamente.

Es necesario crear las siguientes “Capas” (layers):

  • Player
  • Enemy
  • DamageObject

Estas capas nos ayudarán a distinguir que objetos entran en contacto con otros y así poder decidir si es necesario restar o sumar salud a nuestro personaje.

A continuación, es necesario asignarle la capa “Player” a nuestro personaje (ahora nuestro personaje tendrá “Player” tanto en la capa como en el tag y hasta en el nombre 😀 ).

4.2 Sistema de Salud – El Código.

El primero de los cuatro códigos que trabajaremos en este tutorial será el código del sistema de salud del personaje o “HealthSystem.cs” el cual se encargará de administrar todos los aspectos relacionados con la salud del personaje tales como “salud máxima”, “salud actual”, “recibir daño”, “muerte”, etc.

using UnityEngine;
using UnityEngine.Events;
using System.Collections;

// Clase principal del sistema de salud para entidades del juego (jugadores, enemigos, etc.)
public class HealthSystem : MonoBehaviour
{
    // Sección de configuración básica de salud en el inspector de Unity
    [Header("Configuración de Salud")]
    [SerializeField] private float maxHealth = 100f; // Salud máxima que puede tener la entidad
    [SerializeField] private float currentHealth; // Salud actual (se inicializa en Awake)
    
    // Sección de configuración de regeneración automática de salud
    [Header("Regeneración")]
    [SerializeField] private bool canRegenerate = true; // Indica si la entidad puede regenerar salud
    [SerializeField] private float regenerationRate = 5f; // Velocidad de regeneración en puntos por segundo
    [SerializeField] private float regenerationDelay = 3f; // Tiempo de espera tras recibir daño antes de regenerar
    
    // Sección de configuración de período de invulnerabilidad
    [Header("Invulnerabilidad")]
    [SerializeField] private bool hasInvulnerability = false; // Si la entidad tiene período de invulnerabilidad tras recibir daño
    [SerializeField] private float invulnerabilityDuration = 1f; // Duración del período de invulnerabilidad en segundos
    
    // Sección de eventos que otros scripts pueden suscribir para reaccionar a cambios en la salud
    [Header("Eventos")]
    public UnityEvent<float> onHealthChanged; // Se dispara cuando la salud cambia, pasa la salud actual como parámetro
    public UnityEvent<float> onDamageTaken; // Se dispara al recibir daño, pasa la cantidad de daño como parámetro
    public UnityEvent<float> onHealReceived; // Se dispara al recibir curación, pasa la cantidad de curación como parámetro
    public UnityEvent onDeath; // Se dispara cuando la entidad muere
    public UnityEvent onRevive; // Se dispara cuando la entidad revive
    
    // Variables privadas para control interno del sistema
    private bool isDead = false; // Bandera que indica si la entidad está muerta
    private bool isInvulnerable = false; // Bandera que indica si la entidad es invulnerable temporalmente
    private float lastDamageTime = 0f; // Tiempo en el que se recibió el último daño
    private Coroutine regenerationCoroutine; // Referencia a la corrutina de regeneración para poder detenerla

    // Propiedades públicas de solo lectura para acceder a información del sistema de salud desde otros scripts
    public float MaxHealth => maxHealth; // Salud máxima
    public float CurrentHealth => currentHealth; // Salud actual
    public bool IsDead => isDead; // Estado de muerte
    public bool IsInvulnerable => isInvulnerable; // Estado de invulnerabilidad

    // Método que se ejecuta al crear el objeto, se usa para inicializar valores antes de que empiecen otros scripts
    void Awake()
    {
        // Inicializa la salud actual al valor máximo al inicio
        currentHealth = maxHealth;
    }

    // Método que se ejecuta al iniciar el objeto en la escena
    void Start()
    {
        // Si la regeneración está habilitada, comienza el proceso de regeneración
        if (canRegenerate)
        {
            StartRegeneration();
        }
    }

    #region Daño y Curación
    // Método público para aplicar daño a la entidad
    public void TakeDamage(float damageAmount)
    {
        // Si la entidad está muerta, es invulnerable o el daño es 0 o negativo, no hace nada
        if (isDead || isInvulnerable || damageAmount <= 0) return;

        // Aplica el daño reduciendo la salud actual, asegurándose de que no baje de 0
        currentHealth = Mathf.Clamp(currentHealth - damageAmount, 0, maxHealth);
        // Registra el tiempo en que se recibió el daño
        lastDamageTime = Time.time;

        // Detiene la regeneración si estaba activa
        if (regenerationCoroutine != null)
        {
            StopCoroutine(regenerationCoroutine);
        }

        // Si la entidad sigue viva y puede regenerar, reinicia la regeneración con retraso
        if (canRegenerate && currentHealth > 0)
        {
            regenerationCoroutine = StartCoroutine(DelayedRegeneration());
        }

        // Si tiene invulnerabilidad y no está ya invulnerable, activa el período de invulnerabilidad
        if (hasInvulnerability && !isInvulnerable)
        {
            StartCoroutine(InvulnerabilityTimer());
        }

        // Invoca los eventos correspondientes para notificar del daño recibido y cambio de salud
        onDamageTaken?.Invoke(damageAmount);
        onHealthChanged?.Invoke(currentHealth);

        // Si la salud llega a 0 o menos, ejecuta la muerte
        if (currentHealth <= 0)
        {
            Die();
        }
    }

    // Método público para curar a la entidad
    public void Heal(float healAmount)
    {
        // Si la entidad está muerta o la curación es 0 o negativa, no hace nada
        if (isDead || healAmount <= 0) return;

        // Aumenta la salud actual, asegurándose de no superar la salud máxima
        currentHealth = Mathf.Clamp(currentHealth + healAmount, 0, maxHealth);

        // Invoca los eventos correspondientes para notificar de la curación recibida y cambio de salud
        onHealReceived?.Invoke(healAmount);
        onHealthChanged?.Invoke(currentHealth);
    }

    // Método público para establecer directamente la salud de la entidad
    public void SetHealth(float newHealth)
    {
        // Si la entidad está muerta, no permite cambiar la salud
        if (isDead) return;

        // Establece la nueva salud, asegurándose de que esté dentro de los límites válidos
        currentHealth = Mathf.Clamp(newHealth, 0, maxHealth);

        // Invoca el evento de cambio de salud
        onHealthChanged?.Invoke(currentHealth);

        // Si la nueva salud es 0 o menos, ejecuta la muerte
        if (currentHealth <= 0)
        {
            Die();
        }
    }
    #endregion

    #region Regeneración
    // Método privado para iniciar el proceso de regeneración de salud
    private void StartRegeneration()
    {
        // Si ya hay una corrutina de regeneración en ejecución, la detiene
        if (regenerationCoroutine != null)
        {
            StopCoroutine(regenerationCoroutine);
        }
        // Inicia una nueva corrutina de regeneración
        regenerationCoroutine = StartCoroutine(RegenerateHealth());
    }

    // Corrutina que maneja la regeneración continua de salud
    private IEnumerator RegenerateHealth()
    {
        // Bucle infinito que continúa mientras el objeto exista
        while (true)
        {
            // Si la salud actual es menor que la máxima, la entidad no está muerta 
            // y ha pasado suficiente tiempo desde el último daño
            if (currentHealth < maxHealth && !isDead && Time.time - lastDamageTime >= regenerationDelay)
            {
                // Aumenta la salud según la tasa de regeneración y el tiempo transcurrido
                currentHealth = Mathf.Clamp(currentHealth + regenerationRate * Time.deltaTime, 0, maxHealth);
                // Notifica del cambio de salud
                onHealthChanged?.Invoke(currentHealth);

                // Si se ha alcanzado la salud máxima, espera un segundo antes de continuar
                if (currentHealth >= maxHealth)
                {
                    yield return new WaitForSeconds(1f);
                }
            }
            // Espera hasta el siguiente frame antes de continuar el bucle
            yield return null;
        }
    }

    // Corrutina que espera un tiempo antes de reiniciar la regeneración
    private IEnumerator DelayedRegeneration()
    {
        // Espera el tiempo especificado de retraso
        yield return new WaitForSeconds(regenerationDelay);
        // Si la entidad no está muerta y puede regenerar, inicia el proceso de regeneración
        if (!isDead && canRegenerate)
        {
            StartRegeneration();
        }
    }
    #endregion

    #region Invulnerabilidad
    // Corrutina que maneja el período temporal de invulnerabilidad
    private IEnumerator InvulnerabilityTimer()
    {
        // Activa el estado de invulnerabilidad
        isInvulnerable = true;
        // Espera la duración especificada de invulnerabilidad
        yield return new WaitForSeconds(invulnerabilityDuration);
        // Desactiva el estado de invulnerabilidad
        isInvulnerable = false;
    }
    #endregion

    #region Muerte y Revivir
    // Método privado que se ejecuta cuando la entidad muere
    private void Die()
    {
        // Si ya está muerta, no hace nada (previene muertes múltiples)
        if (isDead) return;

        // Marca la entidad como muerta
        isDead = true;
        // Establece la salud a 0
        currentHealth = 0;

        // Detiene cualquier proceso de regeneración en curso
        if (regenerationCoroutine != null)
        {
            StopCoroutine(regenerationCoroutine);
        }

        // Invoca el evento de muerte para notificar a otros sistemas
        onDeath?.Invoke();
    }

    // Método público para revivir a la entidad
    public void Revive(float healthPercentage = 1f)
    {
        // Si la entidad no está muerta, no se puede revivir
        if (!isDead) return;

        // Marca la entidad como viva
        isDead = false;
        // Establece la salud a un porcentaje de la salud máxima (por defecto 100%)
        currentHealth = maxHealth * healthPercentage;

        // Si puede regenerar, reinicia el proceso de regeneración
        if (canRegenerate)
        {
            StartRegeneration();
        }

        // Invoca los eventos de revivir y cambio de salud
        onRevive?.Invoke();
        onHealthChanged?.Invoke(currentHealth);
    }
    #endregion

    #region Utilidades
    // Método público que devuelve el porcentaje de salud restante (0.0 a 1.0)
    public float GetHealthPercentage()
    {
        return currentHealth / maxHealth;
    }

    // Método público que indica si la entidad tiene la salud al máximo
    public bool IsFullHealth()
    {
        return currentHealth >= maxHealth;
    }
    #endregion
}

Este código es muy útil porque no solo es apropiado para el personaje principal, sino también puede ser añadido a Enemigos, NPC’s u otros objetos del juego.

No olvides añadir este código a tu personaje. Recomendamos desactivar las opciones de “Regeneración” e “Invulnerabilidad” por el momento.

4.3 Los que Causan Daño

Vamos a crear un objeto que le produzca daño (le reduzca la salud) a nuestro personaje. Para esto es necesario hacer lo siguiente:

  1. Crea un nuevo objeto de color rojo en la escena y nómbralo como “Hazard” (nosotros usamos cubos pero tu puedes usar el objeto que desees).
  2. Asígnale la capa “DamageObject” (no es necesario asignarle un tag).
  3. Asegúrate de que el “Collider” de tu objeto tenga activa la propiedad “IsTrigger“.
  4. Añade el componente “Audio Source“.

El segundo código que vamos a crear será el que nos permita hacerle daño a nuestro personaje; a este código lo vamos a llamar “DamageDealer.cs“.

using UnityEngine;

// Clase para objetos que pueden causar daño a otras entidades del juego
public class DamageDealer : MonoBehaviour
{
    // Sección de configuración del daño en el inspector de Unity
    [Header("Configuración de Daño")]
    [SerializeField] private float damageAmount = 10f; // Cantidad de daño que inflige este objeto
    [SerializeField] private bool destroyOnHit = true; // Indica si el objeto se destruye al impactar con un objetivo
    [SerializeField] private LayerMask targetLayers; // Máscara de capas que determina qué objetos pueden recibir daño de este dealer

    // Sección de efectos visuales y de audio al impactar
    [Header("Efectos")]
    [SerializeField] private GameObject hitEffect; // Efecto visual que se instancia al impactar (opcional)
    [SerializeField] private AudioClip hitSound; // Sonido que se reproduce al impactar (opcional)

    // Referencia al componente de audio del objeto
    private AudioSource audioSource;

    // Método que se ejecuta al iniciar el objeto en la escena
    void Start()
    {
        // Obtiene el componente AudioSource si existe en el objeto
        audioSource = GetComponent<AudioSource>();
    }

    // Método que se ejecuta cuando este objeto (con collider trigger) entra en contacto con otro collider
    private void OnTriggerEnter(Collider other)
    {
        // Verifica si el objeto con el que colisionó está en las capas objetivo
        if (IsTargetLayer(other.gameObject))
        {
            // Inflige daño al objeto objetivo
            DealDamage(other.gameObject);
            // Reproduce efectos visuales y de audio del impacto
            HandleHitEffects();
        }
    }


    // Método privado que se encarga de aplicar el daño al objetivo
    private void DealDamage(GameObject target)
    {
        // Busca el componente HealthSystem en el objeto objetivo
        HealthSystem healthSystem = target.GetComponent<HealthSystem>();
        
        // Si el objetivo tiene un sistema de salud, le aplica el daño
        if (healthSystem != null)
        {
            healthSystem.TakeDamage(damageAmount);
        }

        // Si está configurado para destruirse al impactar, destruye este objeto
        if (destroyOnHit)
        {
            Destroy(gameObject);
        }
    }

    // Método privado que maneja los efectos visuales y de audio al impactar
    private void HandleHitEffects()
    {
        // Si se ha asignado un efecto visual, lo instancia en la posición actual
        if (hitEffect != null)
        {
            Instantiate(hitEffect, transform.position, Quaternion.identity);
        }

        // Si se ha asignado un sonido y existe el componente de audio, reproduce el sonido
        if (hitSound != null && audioSource != null)
        {
            audioSource.PlayOneShot(hitSound);
        }
    }

    // Método privado que verifica si un objeto objetivo pertenece a las capas permitidas
    private bool IsTargetLayer(GameObject target)
    {
        // Comprueba si la capa del objeto objetivo está incluida en la máscara de capas objetivo
        // Utiliza operaciones binarias para comparar la capa del objetivo con la máscara
        return ((1 << target.layer) & targetLayers) != 0;
    }
}

Este código nos permite reducirle la salud a nuestro personaje cuando el lo toca.

No olvides asignar este código a tu objeto “Hazard“. Además, es necesario que selecciones la capa “Player” en la sección “Target Layers” de este módulo (el código permite seleccionar más de una capa para causarles daño, pero por el momento solo es necesario que se seleccione la que corresponde a nuestro personaje).

Los campos dentro de la sección “Efectos” los dejaremos vacíos por el momento (el código permite usar un sonido y un efecto especial para ser mostrados cuando el daño se realice).

En este punto podrás oprimir “Play” para probar que no existan errores y que la salud en “Current Health” (dentro de “Health System“) disminuya cada vez que tu personaje toca al objeto “Hazard“.

4.4 El Objeto que Cura.

Ya tenemos un objeto que hace daño, ahora es necesario tener un objeto que nos cure. Y para esto, es necesario hacer lo siguiente:

  1. Crea un nuevo objeto de color verde en la escena y nómbralo como “Potion” (nosotros usamos cubos pero tu puedes usar el objeto que desees).
  2. No es necesario asignarle tag ni layer.
  3. Asegúrate de que el “Collider” de tu objeto tenga activa la propiedad “IsTrigger“.
  4. Añade el componente “Audio Source“.

El tercer código que vamos a crear será el que nos permita curar o añadir salud a nuestro personaje; a este código lo vamos a llamar “HealingPickup.cs“.

using UnityEngine;

// Clase para objetos que curan al jugador cuando entra en contacto con ellos
public class HealingPickup : MonoBehaviour
{
    // Sección de configuración de la curación en el inspector de Unity
    [Header("Configuración de Curación")]
    [SerializeField] private float healAmount = 25f; // Cantidad de salud que se recupera al recoger este objeto
    [SerializeField] private bool destroyOnPickup = true; // Indica si el objeto se destruye después de ser recogido

    // Sección de configuración de la animación visual del objeto
    [Header("Animación")]
    [SerializeField] private float rotationSpeed = 50f; // Velocidad de rotación del objeto en grados por segundo
    [SerializeField] private float bobSpeed = 2f; // Velocidad del movimiento de flotación
    [SerializeField] private float bobHeight = 0.5f; // Altura máxima del movimiento de flotación

    // Sección de efectos visuales y de audio al ser recogido
    [Header("Efectos")]
    [SerializeField] private GameObject pickupEffect; // Efecto visual que se instancia al recoger el objeto (opcional)
    [SerializeField] private AudioClip pickupSound; // Sonido que se reproduce al recoger el objeto (opcional)

    // Variables privadas para la animación y componentes
    private Vector3 startPosition; // Posición inicial del objeto para la animación de flotación
    private AudioSource audioSource; // Referencia al componente de audio del objeto

    // Método que se ejecuta al iniciar el objeto en la escena
    void Start()
    {
        // Guarda la posición inicial para usarla en la animación de flotación
        startPosition = transform.position;
        // Obtiene el componente AudioSource si existe en el objeto
        audioSource = GetComponent<AudioSource>();
    }

    // Método que se ejecuta cada frame para actualizar la animación del objeto
    void Update()
    {
        // Animación de rotación: rota el objeto continuamente en el eje Y
        transform.Rotate(0, rotationSpeed * Time.deltaTime, 0);
        
        // Animación de flotación: mueve el objeto verticalmente usando una función seno
        transform.position = startPosition + Vector3.up * Mathf.Sin(Time.time * bobSpeed) * bobHeight;
    }

    // Método que se ejecuta cuando este objeto (con collider trigger) entra en contacto con otro collider
    private void OnTriggerEnter(Collider other)
    {
        // Busca el componente HealthSystem en el objeto con el que colisionó
        HealthSystem healthSystem = other.GetComponent<HealthSystem>();
        
        // Si el objeto que colisionó tiene un sistema de salud
        if (healthSystem != null)
        {
            // Aplica la curación al sistema de salud encontrado
            healthSystem.Heal(healAmount);
            // Maneja los efectos de recogida (sonido, efecto visual y destrucción)
            HandlePickup();
        }
    }

    // Método privado que maneja los efectos cuando el objeto es recogido
    private void HandlePickup()
    {
        // Si se ha asignado un efecto visual, lo instancia en la posición actual
        if (pickupEffect != null)
        {
            Instantiate(pickupEffect, transform.position, Quaternion.identity);
        }

        // Si se ha asignado un sonido y existe el componente de audio, reproduce el sonido
        if (pickupSound != null && audioSource != null)
        {
            audioSource.PlayOneShot(pickupSound);
        }

        // Si está configurado para destruirse al ser recogido, destruye este objeto
        if (destroyOnPickup)
        {   // Si se ha asignado un sonido y existe el componente de audio, se desactiva el collider del objeto y después de reproducir el sonido se destruye todo el objeto
            //Si no están asignados entonces se destruye todo el objeto
            if (pickupSound != null && audioSource != null)
        {
            Collider my_Collider = GetComponent<Collider>();
            my_Collider.enabled = false;
            Destroy(gameObject,pickupSound.length);

        }   else {
            Destroy(gameObject); 
            }
        } 
    }
}

Este código nos permite aumentarle la salud a nuestro personaje cuando el lo toca.

No olvides asignar este código a tu objeto “Potion“.

Los campos dentro de la sección “Efectos” los dejaremos vacíos por el momento (el código permite usar un sonido y un efecto especial para ser mostrados cuando el objeto se use).

En este punto podrás oprimir “Play” para probar que no existan errores y que la salud en “Current Health” (dentro de “Health System“) disminuya cada vez que tu personaje toca al objeto “Hazard” y que la salud se eleva cuando tocas al objeto “Potion” (y que este desaparece).

4.5 La UI.

Para poder visualizar el estado de salud de nuestro personaje, es necesario crear un código y los objetos de la UI (interfaz de usuario) siguientes:

  1. En nuestra escena, dentro de “Hierarchy” vamos a crear un elemento “Canvas” (“Clic Derecho“→UICanvas); el nombre y las propiedades las vamos a dejar tal cual (automáticamente se añadirá el objeto “EventSystem” el cual nos ayuda a manipular botones, selectores, barras desplazadoras y elementos similares; este objeto no lo usaremos pero lo vamos a dejar).
  2. Dentro de “Canvas” vamos a crear un objeto tipo “Slider” (“Clic Derecho sobre Canvas“→UISlider) y lo llamaremos “Health Bar“.
  3. Dentro de “Health Bar” encontraremos tres objetos; vamos a desactivar a “Handle Slide Area“.
  4. Dentro de “Canvas” vamos a crear un objeto tipo “Text (TMP)” (“Clic Derecho sobre Canvas“→UI→”Text-TextMeshPro“) y lo llamaremos “Health Text“. Si al añadirlo el sistema nos pide incluir archivos complementarios de “Text Mesh Pro”, daremos aceptar oprimiendo en la primera de las dos opciones que nos mostrará.
  5. Modificaremos las posiciones y propiedades de “Health Bar” y  “Health Text” para ubicarlos en alguno de los extremos de la pantalla (o como se muestran en el video demostrativo de este tutorial).

Con esto tendremos una barra de salud que se llenará respecto a la cantidad de salud presente en nuestro “Health System” así como un indicador numérico que mostrará también la cantidad de salud actual.

El código que controlará a estos  elementos de la UI será “HealthUI.cs

using UnityEngine;
using UnityEngine.UI;
using TMPro;

// Clase para la interfaz de usuario que muestra la barra de salud del jugador
public class HealthUI : MonoBehaviour
{
    // Sección de referencias a los elementos de la interfaz de usuario
    [Header("Referencias UI")]
    [SerializeField] private Slider healthSlider; // Referencia al control deslizante que muestra la salud
    [SerializeField] private TextMeshProUGUI healthText; // Referencia al texto que muestra valores numéricos de salud
    [SerializeField] private Image healthBarImage; // Referencia a la imagen de la barra de salud para cambiar su color

    // Sección de configuración de colores según el nivel de salud
    [Header("Colores")]
    [SerializeField] private Color highHealthColor = Color.green; // Color cuando la salud es alta (>60%)
    [SerializeField] private Color mediumHealthColor = Color.yellow; // Color cuando la salud es media (30-60%)
    [SerializeField] private Color lowHealthColor = Color.red; // Color cuando la salud es baja (<30%)

    // Sección de configuración de animación de la barra de salud
    [Header("Animación")]
    [SerializeField] private float lerpSpeed = 5f; // Velocidad de interpolación para la animación suave de la barra

    // Variables privadas para el funcionamiento del sistema
    private HealthSystem healthSystem; // Referencia al sistema de salud del jugador
    private float targetHealth; // Valor objetivo de salud para la animación suave

    // Método que se ejecuta al iniciar el objeto en la escena
    void Start()
    {
        // Buscar el sistema de salud en el jugador usando la etiqueta "Player"
        GameObject player = GameObject.FindGameObjectWithTag("Player");
        if (player != null)
        {
            // Obtiene el componente HealthSystem del jugador
            healthSystem = player.GetComponent<HealthSystem>();
            if (healthSystem != null)
            {
                // Se suscribe al evento onHealthChanged del sistema de salud para actualizar la UI
                healthSystem.onHealthChanged.AddListener(UpdateHealthUI);
                // Inicializa la interfaz de usuario con los valores actuales
                InitializeUI();
            }
        }
    }

    // Método privado que inicializa la interfaz de usuario con los valores iniciales del sistema de salud
    private void InitializeUI()
    {
        // Si existe el slider de salud, configura sus valores máximo y actual
        if (healthSlider != null)
        {
            healthSlider.maxValue = healthSystem.MaxHealth;
            healthSlider.value = healthSystem.CurrentHealth;
        }

        // Si existe el texto de salud, muestra el valor actual y máximo
        if (healthText != null)
        {
            healthText.text = $"{healthSystem.CurrentHealth}/{healthSystem.MaxHealth}";
        }

        // Establece el valor objetivo de salud para la animación
        targetHealth = healthSystem.CurrentHealth;

        // Actualiza el color de la barra de salud según el porcentaje actual
        UpdateHealthBarColor(targetHealth / healthSystem.MaxHealth);
    }

    // Método privado que se llama cuando cambia la salud del jugador (suscripción al evento)
    private void UpdateHealthUI(float currentHealth)
    {
        // Actualiza el valor objetivo de salud para la animación
        targetHealth = currentHealth;

        // Si existe el texto de salud, actualiza su contenido con los valores redondeados
        if (healthText != null)
        {
            healthText.text = $"{Mathf.RoundToInt(currentHealth)}/{Mathf.RoundToInt(healthSystem.MaxHealth)}";
        }

        // Actualiza el color de la barra de salud según el porcentaje actual
        UpdateHealthBarColor(currentHealth / healthSystem.MaxHealth);
    }

    // Método que se ejecuta cada frame para actualizar la animación de la barra de salud
    void Update()
    {
        // Animación suave del slider: interpola suavemente entre el valor actual y el objetivo
        if (healthSlider != null)
        {
            healthSlider.value = Mathf.Lerp(healthSlider.value, targetHealth, lerpSpeed * Time.deltaTime);
        }
    }

    // Método privado que actualiza el color de la barra de salud según el porcentaje de salud
    private void UpdateHealthBarColor(float healthPercentage)
    {
        // Si no existe la imagen de la barra de salud, no hace nada
        if (healthBarImage == null) return;

        // Determina el color objetivo según el porcentaje de salud
        Color targetColor;
        if (healthPercentage > 0.6f)
        {
            // Salud alta: verde
            targetColor = highHealthColor;
        }
        else if (healthPercentage > 0.3f)
        {
            // Salud media: amarillo
            targetColor = mediumHealthColor;
        }
        else
        {
            // Salud baja: rojo
            targetColor = lowHealthColor;
        }

        // Aplica el color determinado a la imagen de la barra de salud
        healthBarImage.color = targetColor;
    }
}

Como buena práctica, este tipo de códigos deben ser añadidos a objetos de la escena dedicados a supervisar lo que sucede en nuestro juego. Por ello es necesario:

  1. Crear un nuevo objeto en la escena al cuál llamaremos “Game Manager“.
  2. Dentro de “Game Manager” vamos a crear otro objeto vacío al cuál llamaremos “UI Manager“.
  3. Añadiremos nuestro código “HealthUI.cs” dentro de “UI Manager“.

Dentro del nuevo componente “HealthUI” en la sección de “Referencias UI” vamos a añadir:

  • Nuestro objeto del Canvas “Health Bar” en “Health Slider“.
  • Nuestro objeto del Canvas “Health Text” en “Health Text“.
  • El objeto “Fill” (“Health Bar“→”Fill Area“→”Fill“) en “Health Bar Image

Con esto se podrán enlazar los objetos de UI con el “Health System” de nuestro personaje.

¡Ahora sí, el Momento ha llegado, Oprime Play y disfruta tu Sistema de Salud en tu Primer Juego 3D 🙂 ! 

Ejercicios.

Para reforzar lo aprendido, es necesario practicarlo, por ello intenta realizar los siguientes ejercicios:

  1. Prueba las opciones de “Invulnerabilidad” y “Regeneración” presentes en el “Health System” de tu personaje.
  2. Añade efectos especiales y clips de audio para probar las funcionalidades de los objetos que dañan y de los que curan al personaje.
  3. Crea un mensaje de “Game Over” en el canvas y adecúalo para que se muestre solo cuando el personaje muere (revisa el evento “On Death” que hay en el componente “Health System“)
  4. El código “DamageDealer.cs” necesita ser actualizado para que se comporte como lo hace “HealingPickup.cs” al momento de seleccionar la opción de destruirse al ser tocado; revisa detalladamente ambos códigos y haz las pruebas necesarias para que encuentres cuál es la parte que debe ser modificada.

Este Tutorial de Unity termina aquí. Acompáñanos en el siguiente tutorial donde añadiremos más funcionalidades a “Tu Primer Juego 3D”.

Siguiente Tutorial de Unity: “5. Enemigos 3D

¿Te resultó útil este Tutorial?
¡¡Recuerda, los Anuncios nos Ayudan a Mantener este “Gran Sitio” 😀 !!

Comparte el Post
Posted in PrimerJuego3D.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.