En este final de temporada crearemos un “Sistema de Monedas” y una zona que funcione como Trigger para ejecutar eventos en nuestro “Primer Juego 3D” en Unity 🙂
Para ser más precisos, crearemos un sistema que nos ayude a recolectar monedas, contabilizarlas y mostrar el acumulado en pantalla; además, crearemos “triggers” o zonas invisibles donde al entrar en contacto con el jugador se puedan ejecutar ciertos eventos como en nuestro caso será en mensaje “You Win” simulando que terminaste el nivel.
Tutorial de Unity Nivel: Principiante.
6.1 Los Preparativos.
Para crear nuestro sistema de monedas necesitamos hacer lo siguiente:
Para la moneda:
- Hay que crear una esfera color amarillo a la que llamaremos “Coin” con collider tipo trigger (IsTrigger activo) y un “AudioSource”. No es necesario asignarle tag ni layer.
Para la UI:
- Crearemos un texto como lo hicimos en los tutoriales anteriores que nos servirá para mostrar la cantidad de monedas que tenemos y al que llamaremos “CoinsQty“. También crearemos otro texto que será estático y nos sirva para identificar a que se refiere la cantidad que estamos mostrando, a este texto lo llamaremos “CoinsTxt” y recomendamos que este texto muestre la palabra “Coins“.
Para la supervisión:
- Dentro de nuestro “GameManager” vamos a crear un objeto vacío al cual llamaremos “Coin_Manager“.
6.2 Los Códigos.
Para la recolección y administración de las monedas crearemos tres códigos. El primero de ellos será el administrador de monedas “CoinManager.cs“
using UnityEngine;
using UnityEngine.Events; // Necesario para usar UnityEvent
/// <summary>
/// Sistema de gestión de monedas del juego.
/// Implementa un patrón Singleton para garantizar una única instancia en toda la aplicación.
/// Permite añadir, restar y consultar monedas, además de notificar cambios a través de eventos.
/// </summary>
public class CoinManager : MonoBehaviour
{
/// <summary>
/// Instancia única (Singleton) del administrador de monedas.
/// Permite acceder al sistema de monedas desde cualquier parte del código.
/// </summary>
public static CoinManager Instance { get; private set; }
[Header("Configuración")]
[Tooltip("Cantidad inicial de monedas cuando comienza el juego")]
[SerializeField] private int startingCoins = 0; // Monedas con las que inicia el jugador
[Header("Eventos")]
[Tooltip("Evento que se dispara cuando la cantidad de monedas cambia. Pasa la cantidad actual como parámetro.")]
public UnityEvent<int> onCoinsChanged; // Se llama cuando cambia la cantidad de monedas
/// <summary>
/// Almacena la cantidad actual de monedas del jugador.
/// Es una variable privada para controlar su modificación únicamente a través de métodos públicos.
/// </summary>
private int currentCoins = 0;
/// <summary>
/// Propiedad pública de solo lectura para acceder a las monedas actuales.
/// Proporciona una forma segura de consultar el valor sin permitir modificaciones directas.
/// </summary>
public int CurrentCoins => currentCoins;
/// <summary>
/// Método llamado al inicializar el objeto, incluso si el script no está habilitado.
/// Se utiliza para implementar el patrón Singleton y establecer la cantidad inicial de monedas.
/// </summary>
void Awake()
{
// Implementación del patrón Singleton para garantizar una única instancia
if (Instance == null)
{
// Si no existe una instancia, esta se convierte en la instancia única
Instance = this;
// Hace que el objeto no se destruya al cargar nuevas escenas
DontDestroyOnLoad(gameObject);
// Inicializa las monedas con el valor de partida
currentCoins = startingCoins;
}
else
{
// Si ya existe una instancia, destruye este objeto duplicado
Destroy(gameObject);
}
}
/// <summary>
/// Añade monedas al total actual del jugador.
/// Verifica que la cantidad a añadir sea positiva antes de realizar la operación.
/// </summary>
/// <param name="amount">Cantidad de monedas a añadir (debe ser mayor que 0)</param>
public void AddCoins(int amount)
{
// Verifica que la cantidad a añadir sea válida
if (amount <= 0) return;
// Incrementa el total de monedas
currentCoins += amount;
// Dispara el evento notificando el cambio, pasando la nueva cantidad
onCoinsChanged?.Invoke(currentCoins);
// Muestra en consola la operación realizada para propósitos de depuración
Debug.Log($"Monedas recolectadas: {amount}. Total: {currentCoins}");
}
/// <summary>
/// Resta monedas del total actual del jugador.
/// Verifica que haya suficientes monedas antes de realizar la operación.
/// </summary>
/// <param name="amount">Cantidad de monedas a restar (debe ser mayor que 0)</param>
/// <returns>True si se pudo restar (había suficientes monedas), false en caso contrario</returns>
public bool RemoveCoins(int amount)
{
// Verifica que la cantidad a restar sea válida
if (amount <= 0) return true;
// Verifica que haya suficientes monedas para realizar la operación
if (currentCoins < amount) return false;
// Decrementa el total de monedas
currentCoins -= amount;
// Dispara el evento notificando el cambio, pasando la nueva cantidad
onCoinsChanged?.Invoke(currentCoins);
// Muestra en consola la operación realizada para propósitos de depuración
Debug.Log($"Monedas gastadas: {amount}. Total: {currentCoins}");
// Retorna true indicando que la operación fue exitosa
return true;
}
/// <summary>
/// Establece la cantidad de monedas directamente, reemplazando el valor actual.
/// Útil para inicializaciones específicas o trampas/debugging.
/// </summary>
/// <param name="amount">Nueva cantidad de monedas (no puede ser negativa)</param>
public void SetCoins(int amount)
{
// Asegura que la cantidad no sea negativa, usando el máximo entre 0 y el valor proporcionado
currentCoins = Mathf.Max(0, amount);
// Dispara el evento notificando el cambio, pasando la nueva cantidad
onCoinsChanged?.Invoke(currentCoins);
}
/// <summary>
/// Devuelve la cantidad actual de monedas sin modificar el valor.
/// Método alternativo para acceder al valor mediante una función.
/// </summary>
/// <returns>Cantidad actual de monedas</returns>
public int GetCoins()
{
// Retorna la cantidad actual de monedas
return currentCoins;
}
}
Este código lo asignaremos al objeto “Coin_Manager“:
Nota: Este código está diseñado para que persista entre escenas de tu juego mientras este activo y pueda ir almacenando la cantidad de monedas que vayas recolectando siempre y cuando sea asignado a un objeto vacío que se encuentre en la raíz del “Hierarchy” (como el objeto “GameManager“). En nuestro caso, como solo queremos que funcione dentro de esta escena lo hemos colocado dentro de un objeto “hijo” (“Coin_Manager” es hijo de “GameManager“) para que no persista entre escenas.
El siguiente código es “CoinUI.cs” que administrará la UI que se encarga de mostrar la cantidad de monedas en pantalla:
using UnityEngine;
using UnityEngine.UI; // Necesario para trabajar con componentes UI como Image
using TMPro; // Necesario para trabajar con TextMeshPro
/// <summary>
/// Clase encargada de manejar la interfaz de usuario (UI) para mostrar la cantidad de monedas.
/// Se suscribe al evento onCoinsChanged del CoinManager para actualizar automáticamente el texto
/// y reproducir animaciones cuando la cantidad de monedas cambia.
/// </summary>
public class CoinUI : MonoBehaviour
{
[Header("Referencias UI")]
[Tooltip("Texto donde se mostrará la cantidad de monedas")]
[SerializeField] private TextMeshProUGUI coinText; // Referencia al componente TextMeshProUGUI para mostrar el número de monedas
[Tooltip("Imagen del ícono de moneda (opcional)")]
[SerializeField] private Image coinIcon; // Referencia al componente Image para el ícono de moneda (puede no usarse en este código)
[Tooltip("Componente Animator para reproducir animaciones cuando se recolectan monedas")]
[SerializeField] private Animator coinAnimator; // Referencia al componente Animator para animaciones
[Header("Animación")]
[Tooltip("Nombre del trigger del Animator que se activará cuando se recolecte una moneda")]
[SerializeField] private string coinCollectAnimation = "CoinCollect"; // Nombre del trigger de animación
[Tooltip("Duración de la animación (no se usa directamente en este código)")]
[SerializeField] private float animationDuration = 0.5f; // Duración configurada para la animación (información auxiliar)
/// <summary>
/// Almacena la cantidad de monedas de la última actualización.
/// Se utiliza para determinar si se han añadido monedas y reproducir la animación correspondiente.
/// </summary>
private int lastCoinCount = 0; // Variable para almacenar el conteo anterior de monedas
/// <summary>
/// Método llamado al inicializar el objeto.
/// Se suscribe al evento onCoinsChanged del CoinManager para recibir notificaciones
/// cuando la cantidad de monedas cambia y actualiza la UI con el valor inicial.
/// </summary>
void Start()
{
// Verificar si la instancia de CoinManager existe antes de suscribirse al evento
if (CoinManager.Instance != null)
{
// Suscribirse al evento de cambio de monedas del CoinManager
// Cada vez que las monedas cambien, se llamará al método UpdateCoinDisplay
CoinManager.Instance.onCoinsChanged.AddListener(UpdateCoinDisplay);
// Inicializar la UI con el valor actual de monedas obtenido del CoinManager
UpdateCoinDisplay(CoinManager.Instance.GetCoins());
}
else
{
// Mostrar un error en la consola si no se encuentra el CoinManager
Debug.LogError("CoinManager no encontrado. Asegúrate de que exista en la escena.");
}
}
/// <summary>
/// Método llamado cuando el objeto se destruye.
/// Es importante desuscribirse de los eventos para evitar errores de memoria
/// si el objeto se destruye pero el evento sigue intentando llamar a sus métodos.
/// </summary>
void OnDestroy()
{
// Verificar nuevamente si la instancia de CoinManager existe antes de desuscribirse
if (CoinManager.Instance != null)
{
// Desuscribirse del evento onCoinsChanged para prevenir errores
CoinManager.Instance.onCoinsChanged.RemoveListener(UpdateCoinDisplay);
}
}
/// <summary>
/// Actualiza la visualización de monedas en la UI.
/// Este método es llamado automáticamente cuando cambia la cantidad de monedas.
/// Actualiza el texto mostrado y reproduce una animación si se han añadido monedas.
/// </summary>
/// <param name="coinCount">Cantidad actual de monedas recibida del evento</param>
private void UpdateCoinDisplay(int coinCount)
{
// Verificar si la referencia al texto de monedas existe
if (coinText != null)
{
// Actualizar el texto con la cantidad actual de monedas convertida a string
coinText.text = coinCount.ToString();
}
// Verificar si se han añadido monedas (la nueva cantidad es mayor que la anterior)
// y si el componente Animator existe para poder reproducir la animación
if (coinCount > lastCoinCount && coinAnimator != null)
{
// Activar el trigger de animación definido en el Animator Controller
// Esto reproducirá la animación configurada para la recolección de monedas
coinAnimator.SetTrigger(coinCollectAnimation);
}
// Actualizar la variable lastCoinCount con el nuevo valor para la próxima comparación
lastCoinCount = coinCount;
}
}
Este código lo asignaremos al objeto “Coin_Manager” y nos aseguraremos que nuestro objeto de texto “CoinsQty” este referenciado en la sección “Coin Text“.
Por último crearemos el código para las monedas “Coin.cs“:
using UnityEngine; // Importa la biblioteca principal de Unity
/// <summary>
/// Clase que representa una moneda recolectable en el juego.
/// Gestiona la animación visual de la moneda, la detección de recolección por el jugador
/// y la notificación al sistema global de monedas.
/// </summary>
public class Coin : MonoBehaviour
{
[Header("Configuración de Moneda")]
[Tooltip("Cantidad de monedas que se añaden al recolectar esta moneda")]
[SerializeField] private int coinValue = 1; // Valor de esta moneda
[Tooltip("Velocidad de rotación en grados por segundo")]
[SerializeField] private float rotationSpeed = 90f; // Velocidad de rotación
[Tooltip("Velocidad del movimiento de flotación (frecuencia)")]
[SerializeField] private float bobSpeed = 2f; // Velocidad de flotación
[Tooltip("Altura máxima del movimiento de flotación")]
[SerializeField] private float bobHeight = 0.5f; // Altura de flotación
[Tooltip("Prefab del efecto visual que se muestra al recolectar la moneda")]
[SerializeField] private GameObject collectEffect; // Efecto al recolectar
[Tooltip("Sonido que se reproduce al recolectar la moneda")]
[SerializeField] private AudioClip collectSound; // Sonido al recolectar
/// <summary>
/// Almacena la posición inicial de la moneda para calcular el movimiento de flotación.
/// </summary>
private Vector3 startPosition;
/// <summary>
/// Referencia al componente AudioSource para reproducir sonidos.
/// </summary>
private AudioSource audioSource;
/// <summary>
/// Bandera que indica si la moneda ya ha sido recolectada para evitar recolecciones múltiples.
/// </summary>
private bool isCollected = false;
/// <summary>
/// Método llamado al inicializar el objeto.
/// Guarda la posición inicial y obtiene la referencia al AudioSource.
/// </summary>
void Start()
{
// Guardar la posición inicial para usarla como punto de referencia en la flotación
startPosition = transform.position;
// Obtener el componente AudioSource adjunto al GameObject
audioSource = GetComponent<AudioSource>();
}
/// <summary>
/// Método llamado cada frame.
/// Controla la animación continua de rotación y flotación de la moneda.
/// </summary>
void Update()
{
// Rotación continua alrededor del eje Y (vertical)
// Vector3.up representa el vector (0,1,0)
// Time.deltaTime asegura que la rotación sea suave e independiente del framerate
transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
// Flotación (movimiento vertical suave usando una función senoidal)
// Mathf.Sin crea una oscilación suave entre -1 y 1
// Time.time * bobSpeed controla la frecuencia de la oscilación
// bobHeight controla la amplitud (altura máxima) de la oscilación
float newY = startPosition.y + Mathf.Sin(Time.time * bobSpeed) * bobHeight;
// Actualizar solo la coordenada Y manteniendo las coordenadas X y Z
transform.position = new Vector3(transform.position.x, newY, transform.position.z);
}
/// <summary>
/// Método llamado cuando otro collider entra en contacto con el trigger de esta moneda.
/// Detecta si el objeto que entra es el jugador para iniciar la recolección.
/// </summary>
/// <param name="other">Collider del objeto que ha entrado en el trigger</param>
private void OnTriggerEnter(Collider other)
{
// Verificar si el objeto que entra es el jugador y si la moneda no ha sido recolectada aún
// Esto evita que la moneda se recolecte múltiples veces
if (other.CompareTag("Player") && !isCollected)
{
// Marcar la moneda como recolectada para evitar recolecciones múltiples
isCollected = true;
// Llamar al método que maneja la lógica de recolección
CollectCoin();
}
}
/// <summary>
/// Método que ejecuta toda la lógica de recolección de la moneda.
/// Reproduce sonidos, efectos visuales, notifica al sistema de monedas y destruye la moneda.
/// </summary>
private void CollectCoin()
{
// Reproducir sonido de recolección si se ha asignado un AudioClip y existe AudioSource
if (collectSound != null && audioSource != null)
{
// PlayOneShot permite reproducir el sonido sin interrumpir otros sonidos
audioSource.PlayOneShot(collectSound);
}
// Instanciar el efecto visual de recolección si se ha asignado
if (collectEffect != null)
{
// Instantiate crea una copia del prefab en la posición actual de la moneda
// Quaternion.identity significa sin rotación adicional
Instantiate(collectEffect, transform.position, Quaternion.identity);
}
// Notificar al sistema global de monedas para aumentar el contador
// Se asume que CoinManager.Instance es accesible y AddCoins está implementado
CoinManager.Instance.AddCoins(coinValue);
// Desactivar el renderizado y el collider para que la moneda desaparezca visualmente
// pero aún permanezca en la escena brevemente para permitir que se reproduzca el sonido
GetComponent<Renderer>().enabled = false;
GetComponent<Collider>().enabled = false;
// Destruir el objeto después de un breve tiempo (0.1 segundos)
// Esto permite que el sonido se reproduzca completamente antes de eliminar el objeto
Destroy(gameObject, 0.1f);
}
}
Este código lo asignaremos a nuestro objeto “Coin” (nuestra moneda).
En este punto ya podremos dar “Play” para probar que se pueden recolectar las monedas y que estas se contabilizan en la pantalla del juego.
6.3 Los “Triggers”.
Para que nuestro personaje pueda desbloquear alguna acción específica al llegar a cierto lugar en nuestro juego, aplicaremos un “Trigger” que será una zona invisible en donde si entra nuestro personaje se ejecutará un evento.
Para nuestro caso, esta zona invisible se ubicará en el lugar en el que nosotros designemos como el final del juego y al llegar ahí después de unos segundos se mostrará el mensaje “You Win“.
Para hacer lo anterior, debemos hacer lo siguiente:
- Crear un texto en la UI con el nombre “YouWinTxt” y que contenga la frase “You Win“. Este texto debe estar ubicado en el centro de la pantalla. Al finaliza, debemos desactivar “YouWinTxt” para que no se vea durante el juego.
- Crear un objeto vacío en nuestro juego al cuál llamaremos “WinArea” (no es necesario que tenga un tag o un layer). A este objeto le asignaremos un collider tipo trigger y haremos nuestro objeto tan grande para que nuestro personaje pueda entrar ahí. El código para el “Trigger” se llamará “EventTrigger.cs” y debe contener lo siguiente:
using UnityEngine;
using UnityEngine.Events; // Permite usar UnityEvent para definir eventos personalizados
using System.Collections; // Necesario para trabajar con corrutinas (IEnumerator)
/// <summary>
/// Componente que actúa como un trigger de eventos con temporizador.
/// Se activa cuando el jugador entra en su área de efecto y puede ejecutar eventos
/// después de un tiempo especificado. Ofrece múltiples modos de activación.
/// </summary>
[RequireComponent(typeof(Collider))] // Asegura que el GameObject tenga un componente Collider
public class EventTrigger : MonoBehaviour
{
[Header("Configuración del Trigger")]
[Tooltip("Tiempo en segundos que debe transcurrir después de entrar para ejecutar el evento")]
[SerializeField] private float triggerDelay = 0f; // Retraso antes de ejecutar el evento principal
[Tooltip("¿El evento se ejecuta una sola vez o puede repetirse?")]
[SerializeField] private bool singleUse = true; // Indica si el trigger solo puede usarse una vez
[Tooltip("¿El evento se ejecuta automáticamente al entrar (sin necesidad de mantenerse dentro)?")]
[SerializeField] private bool triggerOnEnter = true; // Activar evento al entrar en el trigger
[Tooltip("¿El evento se ejecuta cuando el jugador se mantiene dentro durante el tiempo especificado?")]
[SerializeField] private bool triggerOnStay = false; // Activar evento mientras se mantiene dentro
[Tooltip("¿El evento se ejecuta cuando el jugador sale del trigger?")]
[SerializeField] private bool triggerOnExit = false; // Activar evento al salir del trigger
[Header("Eventos")]
[Tooltip("Evento que se ejecuta después del tiempo especificado")]
public UnityEvent OnTriggerActivated; // Evento principal que se ejecuta con retraso
[Tooltip("Evento que se ejecuta cuando el jugador entra en el trigger")]
public UnityEvent OnPlayerEnter; // Evento que se ejecuta cuando el jugador entra
[Tooltip("Evento que se ejecuta cuando el jugador sale del trigger")]
public UnityEvent OnPlayerExit; // Evento que se ejecuta cuando el jugador sale
/// <summary>
/// Indica si el jugador se encuentra actualmente dentro del área del trigger
/// </summary>
private bool playerInside = false;
/// <summary>
/// Indica si el trigger ya ha sido activado cuando es de uso único
/// </summary>
private bool hasBeenTriggered = false;
/// <summary>
/// Referencia a la corrutina actual para poder cancelarla si es necesario
/// </summary>
private Coroutine triggerCoroutine;
/// <summary>
/// Referencia al componente Collider del trigger
/// </summary>
private Collider triggerCollider;
/// <summary>
/// Método llamado al inicializar el componente.
/// Configura el collider como trigger y realiza comprobaciones iniciales.
/// </summary>
void Start()
{
// Obtener y configurar el componente Collider
triggerCollider = GetComponent<Collider>();
if (triggerCollider != null)
{
// Asegurar que el collider funcione como trigger
triggerCollider.isTrigger = true;
}
else
{
// Registrar error si no se encuentra un componente Collider
Debug.LogError("EventTriggerWithTimer necesita un componente Collider para funcionar", this);
}
}
/// <summary>
/// Método llamado cuando otro collider entra en contacto con este trigger.
/// Se utiliza para detectar cuando el jugador entra en el área del trigger.
/// </summary>
/// <param name="other">Collider del objeto que ha entrado en el trigger</param>
void OnTriggerEnter(Collider other)
{
// Verificar si el objeto que entra es el jugador
if (IsPlayer(other.gameObject))
{
// Marcar que el jugador está dentro del trigger
playerInside = true;
// Invocar el evento de entrada del jugador si hay suscriptores
OnPlayerEnter?.Invoke();
// Cancelar cualquier corrutina anterior que estuviera en ejecución
if (triggerCoroutine != null)
{
StopCoroutine(triggerCoroutine);
}
// Iniciar el temporizador si se configura ejecutar al entrar y se puede activar
if (triggerOnEnter && CanTrigger())
{
// Si hay retraso, iniciar la corrutina correspondiente
if (triggerDelay > 0)
{
triggerCoroutine = StartCoroutine(TriggerWithDelay());
}
else
{
// Si no hay retraso, activar inmediatamente
ActivateTrigger();
}
}
}
}
/// <summary>
/// Método llamado mientras otro collider se mantiene dentro de este trigger.
/// Se utiliza para detectar cuando el jugador permanece en el área del trigger.
/// </summary>
/// <param name="other">Collider del objeto que se mantiene dentro del trigger</param>
void OnTriggerStay(Collider other)
{
// Si se debe activar al mantenerse dentro, el objeto es el jugador,
// está dentro y se puede activar el trigger
if (triggerOnStay && IsPlayer(other.gameObject) && playerInside && CanTrigger())
{
// Solo iniciar el temporizador si no está ya en marcha
if (triggerCoroutine == null)
{
// Si hay retraso, iniciar la corrutina correspondiente
if (triggerDelay > 0)
{
triggerCoroutine = StartCoroutine(TriggerWithDelay());
}
else
{
// Si no hay retraso, activar inmediatamente
ActivateTrigger();
}
}
}
}
/// <summary>
/// Método llamado cuando otro collider sale del contacto con este trigger.
/// Se utiliza para detectar cuando el jugador abandona el área del trigger.
/// </summary>
/// <param name="other">Collider del objeto que ha salido del trigger</param>
void OnTriggerExit(Collider other)
{
// Verificar si el objeto que sale es el jugador
if (IsPlayer(other.gameObject))
{
// Marcar que el jugador ya no está dentro del trigger
playerInside = false;
// Invocar el evento de salida del jugador si hay suscriptores
OnPlayerExit?.Invoke();
// Cancelar el temporizador si el jugador sale antes de que se complete
if (triggerCoroutine != null)
{
StopCoroutine(triggerCoroutine);
triggerCoroutine = null;
}
// Ejecutar evento de salida si está configurado y se puede activar
if (triggerOnExit && CanTrigger())
{
// Si hay retraso, iniciar la corrutina correspondiente
if (triggerDelay > 0)
{
StartCoroutine(ExitTriggerWithDelay());
}
else
{
// Si no hay retraso, activar inmediatamente
ActivateTrigger();
}
}
}
}
/// <summary>
/// Corrutina que ejecuta el evento principal después del tiempo especificado.
/// Espera el tiempo configurado y luego activa el trigger.
/// </summary>
/// <returns>IEnumerator para el funcionamiento de la corrutina</returns>
private IEnumerator TriggerWithDelay()
{
// Esperar el tiempo especificado en triggerDelay
yield return new WaitForSeconds(triggerDelay);
// Activar el trigger principal
ActivateTrigger();
// Limpiar la referencia a la corrutina
triggerCoroutine = null;
}
/// <summary>
/// Corrutina para el evento de salida con retraso.
/// Similar a TriggerWithDelay pero específica para la salida del trigger.
/// </summary>
/// <returns>IEnumerator para el funcionamiento de la corrutina</returns>
private IEnumerator ExitTriggerWithDelay()
{
// Esperar el tiempo especificado en triggerDelay
yield return new WaitForSeconds(triggerDelay);
// Activar el trigger principal
ActivateTrigger();
}
/// <summary>
/// Activa el evento principal y maneja el estado de uso único.
/// Este método es el punto central donde se ejecutan las acciones del trigger.
/// </summary>
private void ActivateTrigger()
{
// Verificar si se puede activar el trigger según su configuración
if (CanTrigger())
{
// Invocar el evento principal si hay suscriptores
OnTriggerActivated?.Invoke();
// Si es de un solo uso, marcarlo como ya activado
if (singleUse)
{
hasBeenTriggered = true;
// Opcional: desactivar el trigger después de usarlo
// gameObject.SetActive(false);
}
}
}
/// <summary>
/// Verifica si el objeto especificado es el jugador.
/// Utiliza la etiqueta "Player" para identificar al jugador.
/// </summary>
/// <param name="obj">Objeto a verificar</param>
/// <returns>True si el objeto tiene la etiqueta "Player"</returns>
private bool IsPlayer(GameObject obj)
{
// Comprobar si el objeto tiene la etiqueta "Player"
return obj.CompareTag("Player");
}
/// <summary>
/// Verifica si se puede ejecutar el trigger según la configuración.
/// Considera si es de un solo uso y si ya ha sido activado.
/// </summary>
/// <returns>True si se puede ejecutar (no es de un solo uso o no ha sido activado)</returns>
private bool CanTrigger()
{
// Retornar true si no es de un solo uso o si no ha sido activado aún
return !singleUse || !hasBeenTriggered;
}
/// <summary>
/// Método público para reiniciar el trigger si no es de un solo uso.
/// Permite reutilizar el trigger después de haber sido activado.
/// </summary>
public void ResetTrigger()
{
// Solo reiniciar si no es de un solo uso
if (!singleUse)
{
// Resetear el estado de activación
hasBeenTriggered = false;
// Resetear el estado de presencia del jugador
playerInside = false;
// Cancelar cualquier corrutina en ejecución
if (triggerCoroutine != null)
{
StopCoroutine(triggerCoroutine);
triggerCoroutine = null;
}
}
}
/// <summary>
/// Método público para activar el trigger manualmente.
/// Permite activar el trigger desde otro script o mediante código.
/// </summary>
/// <param name="ignoreSingleUse">Si true, ignora la restricción de un solo uso</param>
public void TriggerManually(bool ignoreSingleUse = false)
{
// Verificar si se puede activar (ignorando singleUse si se especifica)
if (ignoreSingleUse || CanTrigger())
{
// Si hay retraso, iniciar la corrutina correspondiente
if (triggerDelay > 0)
{
StartCoroutine(ManualTriggerWithDelay());
}
else
{
// Si no hay retraso, activar inmediatamente
ActivateTrigger();
}
}
}
/// <summary>
/// Corrutina para trigger manual con retraso.
/// Similar a otras corrutinas pero para activación manual.
/// </summary>
/// <returns>IEnumerator para el funcionamiento de la corrutina</returns>
private IEnumerator ManualTriggerWithDelay()
{
// Esperar el tiempo especificado en triggerDelay
yield return new WaitForSeconds(triggerDelay);
// Activar el trigger principal
ActivateTrigger();
}
/// <summary>
/// Método llamado en el editor para dibujar ayudas visuales (gizmos).
/// Muestra visualmente el área del trigger en la vista de escena.
/// </summary>
void OnDrawGizmos()
{
// Dibujar el área del trigger para facilitar la visualización en el editor
Collider col = GetComponent<Collider>();
if (col != null)
{
// Usar color gris si ya ha sido activado, verde si no
Gizmos.color = hasBeenTriggered ? Color.gray : Color.green;
// Dibujar diferentes formas según el tipo de collider
if (col is BoxCollider)
{
// Dibujar un cubo para BoxCollider
BoxCollider boxCol = col as BoxCollider;
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, transform.lossyScale);
Gizmos.DrawWireCube(boxCol.center, boxCol.size);
}
else if (col is SphereCollider)
{
// Dibujar una esfera para SphereCollider
SphereCollider sphereCol = col as SphereCollider;
Gizmos.DrawWireSphere(transform.position + sphereCol.center, sphereCol.radius);
}
else if (col is CapsuleCollider)
{
// Dibujar una esfera para CapsuleCollider (simplificación)
CapsuleCollider capsuleCol = col as CapsuleCollider;
Vector3 center = transform.position + capsuleCol.center;
Gizmos.DrawWireSphere(center, capsuleCol.radius);
}
}
}
}
Este código debe ser asignado a nuestro objeto “WinArea” y debe tener los siguientes parámetros:
- Trigger Delay = 2
- Single Use (habilitado)
- Trigger On Enter (inhabilitado)
- Trigger On Stay (habilitado)
- Trigger On Exit (inhabilitado)
En la sección de “Eventos” en “On Trigger Activated” debemos asignar la referencia a nuestro objeto de texto “YouWinTxt” para que se active cuando se ejecute el evento.
Recuerda ubicar “WinArea” donde quieras que se encuentre el final de tu juego.
¡Ahora sí, el Momento ha llegado, Oprime Play y disfruta tus Monedas y el Final de tu Primer Juego 3D 🙂 !
Ejercicios.
Para reforzar lo aprendido, es necesario practicarlo, por ello intenta realizar los siguientes ejercicios:
- Prueba todas las opciones que tiene “EventTrigger” para crear diferentes eventos (que aparezcan más obstáculos o enemigos, que desaparezca un puente si no pasas rápido por el, etc).
- Añade efectos especiales y clips de audio para probar las funcionalidades de las Monedas.
- Si tienes experiencia con las Animaciones y “Animators” de Unity, puedes crear animaciones para la UI de tus monedas y asignarlas en “CoinUI” para que veas como funciona por completo el código.
- Utiliza todo lo aprendido en esta serie de tutoriales para que hagas un GRAN JUEGO DE PLATAFORMAS, nosotros sabemos que tu puedes 🙂
Este Tutorial de Unity termina aquí. Esperamos que te haya sido de mucha utilidad y que hayas creado “Tu Primer Juego 3D”.
Unity Tutorial: “Tu Primer Juego 3D“
1. Creando un Juego 3D
2. Supercargando el Juego
3. Añadiendo Plataformas
4. Salud y Daño
5. Enemigos 3D
6. Dinero y Triggers
Ver más Tutoriales






















