Niixer

Desarrollo de videojuego de laberinto haciendo uso del motor unity

Creación del cielo y cómo oscurecer el mapa:

Para comenzar, se descarga un paquete de skybox desde la Asset Store (“AllSky Free – 10 Sky / Skybox Set”). Para ello:

  1. Se accede a window/Package Manager y se selecciona la pestaña My Assets. Además, para buscar el asset seleccionado.
  2. Una vez descargado se importa el paquete explorando en la carpeta del skybox y se arrastra el material seleccionado hacia la escena para asignarlo como fondo del mapa.

A continuación,para oscurecer la escena se modifica la configuración de iluminación desde Window/Rendering/Lighting Settings. Allí se selecciona el skybox descargado en el campo Environment Lighting, y luego se genera la iluminación presionando Generate Lighting.

Buscar los assets y descargarlos e importarlos.

Ajustar la intensidad y el color ambiente se ajustan seleccionando la luz direccional principal, modificando sus valores de Intensity, Filter y Color, logrando así la apariencia nocturna deseada

Para oscurecer el mapa nos vamos a la ruta que se muestra en la imagen

Se debe seleccionar la bolita que tenemos en lighting settings y buscar la del cielo que escogimos. le damos en generate y la luz de nuestro mapa estará implementada.

Se debe oscurecer más la luz, primero seleccionamos la luz del mapa, nos vamos a emisión y en filter ajustamos el color que queremos la luz y la intensidad de esta.

Del mismo modo, que se hizo el proceso de implementación del cielo se implementa la linterna.

Se agrega en el personaje 

La forma como se agregar el efecto de la linterna, le damos click derecho, light y seleccionamos light y después spotlight, la añadimos en nuestro jugador.

Como recomendación adicional, añadir una pared para que la luz refleje para poder ajustar bien el tema de la luz, como primera cosa ajustamos el rango en rango para que ajustar el rango de visión.

Y en intensidad ajustamos la intensidad de la luz.

En shape podemos ajustar la forma en que se ve la luz solo tenemos que mover la semiesfera de la derecha para ajustarla. y la otra semiesfera del lado izquierdo es para ajustar la suavidad del efecto si lo ponemos muy cerca de la otra esfera se verá un círculo pero si lo alejamos pues se ver el efecto de linterna.

Y ahora el paquete de la linterna tiene también incluido unas cookies que nos sirven para dar el efecto de una linterna, ponemos la que queramos y la ponemos en cookie.

Creación de la IA del enemigo:

En primer lugar se descarga el modelo de mixamo.

Antes de ponerlo dentro del mapa, debemos irnos a rig y después pasar el Animation type a humanoid y también de paso volver a descargar el modelo varias veces pero esta vez con las animaciones que necesitemos. estos modelos de animación debemos pasarlos a legacy en animation type.

Luego, se selecciona el modelo descargado y se identifica la animación deseada, que siempre aparece con un icono triangular. Se duplica usando Ctrl + D y luego se puede eliminar el modelo, quedando solo la animación copiada en el proyecto para ser usada de manera independiente.

Después se selecciona al enemigo dentro de la escena y se elimina su componente Animator. Se agrega en cambio un componente llamado Animation, el cual permite asignar manualmente animaciones específicas.

Se añade el componente NavMeshAgent, que se ajusta para que envuelva correctamente la figura del enemigo. Este componente es fundamental para que el personaje pueda navegar automáticamente dentro del mapa usando el NavMesh horneado. Se definen valores como velocidad, radio, altura y comportamiento de frenado, de manera que el desplazamiento sea natural y acorde con el diseño del enemigo.

Luego, se crea un nuevo script y se introduce el código AI mostrado. El script controla todo el comportamiento del enemigo: detección del jugador por rango, persecución, ataques cuerpo a cuerpo, ataques especiales, animaciones y distancia de acción.

using UnityEngine;

using UnityEngine.AI;

public class Rango2 : MonoBehaviour

{

    public NavMeshAgent Monstruo;

    public float Velocidad;

    public bool Persiguiendo;

    public float Rango;

    public float Distancia;

    public Transform Objetivo;

    [Header(“Animaciones”)]

    public Animation Anim;

    public string NombreAnimacionCaminar;

    public string NombreAnimacionQuieto;

    public string NombreAnimacionAtaque;

    public string NombreAnimacionGrito;

    [Header(“Ataque”)]

    public float daño;

    public float RangoAtaque = 2f;

    public float TiempoEntreAtaques = 2f;

    private float tiempoUltimoAtaque;

    private bool estaAtacando = false;

    private void Update()

    {

        Distancia = Vector3.Distance(Monstruo.transform.position, Objetivo.position);

        if (Distancia < Rango)

        {

            Persiguiendo = true;

        }

        else if (Distancia > Rango + 3)

        {

            Persiguiendo = false;

        }

        if (Distancia <= RangoAtaque && Persiguiendo)

        {

            Monstruo.speed = 0;

            if (Time.time >= tiempoUltimoAtaque + TiempoEntreAtaques && !estaAtacando)

            {

                IniciarAtaque();

            }

        }

        else if (Persiguiendo == false)

        {

            Monstruo.speed = 0;

            estaAtacando = false;

            Anim.CrossFade(NombreAnimacionQuieto);

        }

        else if (Persiguiendo == true && !estaAtacando)

        {

            Monstruo.speed = Velocidad;

            Anim.CrossFade(NombreAnimacionCaminar);

            Monstruo.SetDestination(Objetivo.position);

        }

    }

    private void OnDrawGizmosSelected()

    {

        Gizmos.color = Color.green;

        Gizmos.DrawWireSphere(Monstruo.transform.position, Rango);

        Gizmos.color = Color.red;

        Gizmos.DrawWireSphere(Monstruo.transform.position, RangoAtaque);

    }

    private void IniciarAtaque()

    {

        estaAtacando = true;

        tiempoUltimoAtaque = Time.time;

        // Solo reproduce la animación, NO llama a Ataque() aquí

        Anim.CrossFade(NombreAnimacionAtaque);

        // El daño se aplicará desde el Animation Event

        // Termina el ataque después de la duración de la animación

        Invoke(“FinalizarAtaque”, 1.5f); // Ajusta según duración de tu animación

    }

    private void FinalizarAtaque()

    {

        estaAtacando = false;

    }

    // Este método será llamado SOLO desde el Animation Event

    public void Ataque()

    {

        // Verifica que el objetivo siga en rango antes de aplicar daño

        if (Distancia <= RangoAtaque && Objetivo.GetComponent<Codigo_Salud>() != null)

        {

            Objetivo.GetComponent<Codigo_Salud>().RecibirDaño(daño);

            Debug.Log(“Daño aplicado: ” + daño);

        }

    }

}

Después arrastramos el script al personaje y ubicamos cada cosa en su lugar, en monstruo ponemos el mesh que creamos, en animaciones, escribimos el nombre de cada animación que se nos pide de la misma manera que la animaciones que asignamos arriba, y en objetivo añadimos el jugador y en Anim seleccionamos nuestro enemigo, para la creación del daño se explicará más abajo. En rango ponemos la zona en la que queremos que nuestro enemigo detecte al jugador, y en distancia no se pondrá nada por que al correr el juego se ajusta solo.

Por otro lado, en el mapa se crea también un componente llamado NavMesh Surface, que se configura para detectar las áreas caminables y luego se presiona Bake. Esto genera una superficie azul que representa el terreno navegable, sta superficie define los límites por donde la IA podrá caminar o no.

Daño al jugador:

luego, para aplicar daño mediante animaciones, se accede a la ventana Window → Animation → Animation. Allí se selecciona la animación de golpe del enemigo y se busca el fotograma exacto en el que su ataque debería impactar al jugador. En ese punto se presiona el botón “Add Event”, creando un evento especial que ejecutará código justo al ocurrir el golpe.

Después de esto creamos un nuevo script.
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

// Script de Unity | 0 referencias

public class Alienrango : MonoBehaviour

{

    public MonstruoRango Enemigo;

    // 0 referencias

    public void Daño()

    {

        Enemigo.Ataque();

    }

}

Listo ya teniendo el Script para llamar al método daño cuando el enemigo ataca se debe asignar el script al enemigo y después lo que hacemos es irnos al time event que creamos previamente y seleccionamos el método que creamos el de daño y con eso el enemigo solo ataca cuando haga la animación de golpe.

Daño a distancia:

En este caso, para implementar daño a distancia, se actualiza el script principal añadiendo un sistema adicional de daño progresivo. Este tipo de daño es ideal para ataques especiales, como un grito sónico. El código agregado permite que el daño no se aplique de golpe, sino gradualmente, aumentando y disminuyendo siguiendo una curva suave tipo montaña. para ello se agrega lo siguiente:
// ==================== AGREGAR ESTAS VARIABLES ====================

[Header(“Daño Progresivo del Grito”)]

public bool usarDañoProgresivo = true; // Activa/desactiva el sistema

public float dañoInicioFin = 5f; // Daño al inicio y final del grito

public float ticksDañoPorSegundo = 5f; // Cuántas veces por segundo aplica daño

private float dañoActualGrito = 0f;

// ==================== MODIFICAR IniciarGrito() ====================

private void IniciarGrito()

{

    estaGritando = true;

    tiempoUltimoGrito = Time.time;

    Monstruo.speed = 0;

    Anim.CrossFade(NombreAnimacionGrito);

    Debug.Log(“¡El enemigo está gritando!”);

    // AGREGAR ESTO: Si usa daño progresivo, inicia la corrutina

    if (usarDañoProgresivo)

    {

        StartCoroutine(DañoProgresivoGrito(2f)); // 2f = duración total del grito

    }

    Invoke(“FinalizarGrito”, 2f);

}

// ==================== AGREGAR ESTE MÉTODO COMPLETO ====================

// Sistema de daño progresivo (efecto montaña)

private System.Collections.IEnumerator DañoProgresivoGrito(float duracionTotal)

{

    float tiempoTranscurrido = 0f;

    float intervalo = 1f / ticksDañoPorSegundo;

    while (tiempoTranscurrido < duracionTotal)

    {

        float progreso = tiempoTranscurrido / duracionTotal;

        float multiplicador = Mathf.Sin(progreso * Mathf.PI);

        dañoActualGrito = Mathf.Lerp(dañoInicioFin, dañoGrito, multiplicador);

        if (Distancia <= RangoGrito && Objetivo.GetComponent<Codigo_Salud>() != null)

        {

            Objetivo.GetComponent<Codigo_Salud>().RecibirDaño(dañoActualGrito);

            Debug.Log($”Daño progresivo aplicado: {dañoActualGrito:F1} (Progreso: {progreso * 100:F0}%)”);

        }

        yield return new WaitForSeconds(intervalo);

        tiempoTranscurrido += intervalo;

    }

}

// ==================== MODIFICAR AtaqueGrito() ====================

public void AtaqueGrito()

{

    // Si NO usas daño progresivo, aplica daño de golpe

    if (!usarDañoProgresivo)

    {

        if (Distancia <= RangoGrito && Objetivo.GetComponent<Codigo_Salud>() != null)

        {

            Objetivo.GetComponent<Codigo_Salud>().RecibirDaño(dañoGrito);

            Debug.Log(“Daño de grito aplicado: ” + dañoGrito);

        }

    }

}

“`

## Cómo funciona el daño progresivo:

“`

Tiempo:  0s ——– 0.5s ——– 1.0s ——– 1.5s ——– 2.0s

Daño:    5 ——– 10 ——— 15 ——— 10 ——— 5

         ↑           ↑            ↑            ↑            ↑

       Inicio     Subiendo     Pico       Bajando        Fin

“`

### Ejemplo visual:

“`

       15 (daño máximo)    ╱\

                          ╱  \

       10              ╱      \

                     ╱          \

        5 ────────╱              \────────

          Inicio   →   Pico   →   Fin

“`

## Configuración en Unity:

“`

[Daño Progresivo del Grito]

✓ usarDañoProgresivo: true (activado)

dañoInicioFin: 5 (daño al principio y final)

dañoGrito: 15 (daño máximo en el pico)

ticksDañoPorSegundo: 5 (aplica daño 5 veces por segundo)

En resumen, este codigo permite crear daño progresivo dentro del mismo código pero necesitas hacer cambios en la animación creando un time event en la animación del grito

Después de añadir el time event y modificar el código ya lo podemos seleccionar dentro de nuestros métodos para dejarlo listo. También es necesario crear un evento dentro de la animación del grito, igual que con el ataque cuerpo a cuerpo, para sincronizar la animación con la aplicación del daño. Esto garantiza que el ataque sea visual y mecánicamente consistente.

Barra de vida:

Primero se crea un Canvas siguiendo la ruta indicada y dentro de él se coloca una imagen que será la barra de vida. Para una mejor organización, se crea un objeto vacío como padre de la imagen, y cada elemento se nombra de manera clara para evitar confusiones en el futuro

La imagen se posiciona dentro de la pantalla en la ubicación deseada y luego se asigna una imagen de fondo que servirá como interfaz visual de la salud del jugador.

En las propiedades de la imagen modificamos el cuadro de imagen que estaba en el centro al lugar que preferimos y en suerce image seleccionamos la imagen que queramos para que sea nuestra barra de vida pero no se puede quedar vacio.

En raycast padding podemos ajustar como queremos la barra de vida importante que imagetype este filled y ya en fill method podemos escoger como se disminuirá la barra de vida.

La forma en la que se realiza el efecto que muestro en la imagen duplicamos la barra la renombramos y la ponemos encima de la imagen de la barra original después le ponemos el color del fondo y listo.

Para crear el texto de la barra nos vamos a la creación de objeto como se muestra en pantalla.

Ajustamos el texto debajo de la barra de vida para que no quede por debajo de esta en la pantalla y después la ajustamos en el centro de esta. Seguido de esto, se desarrolla un script que maneja el sistema de salud, actualizando el valor numérico y visual de la barra.

// Importa el espacio de nombres para trabajar con colecciones no genéricas

using System.Collections;

// Importa el espacio de nombres para trabajar con colecciones genéricas (como List, Dictionary)

using System.Collections.Generic;

// Importa el espacio de nombres principal de Unity para acceder a componentes y funcionalidades del motor

using UnityEngine;

// Importa el espacio de nombres de Unity para trabajar con elementos de interfaz de usuario (UI)

using UnityEngine.UI;

// Define una clase pública llamada Codigo_Salud que hereda de MonoBehaviour (clase base de Unity para scripts)

public class Codigo_Salud : MonoBehaviour

{

    // Variable pública que almacena la salud actual del jugador, inicializada en 100

    public float Salud = 100;

    // Variable pública que almacena la salud máxima posible del jugador, inicializada en 100

    public float SaludMaxima = 100;

    // Atributo que crea un encabezado “Interfaz” en el Inspector de Unity para organizar variables

    [Header(“Interfaz”)]

    // Variable pública de tipo Image para referenciar la barra de salud visual en la UI

    public Image BarraSalud;

    // Variable pública de tipo Text para referenciar el texto que muestra la salud numérica

    public Text TextoSalud;

    // Método Update se ejecuta automáticamente cada frame (normalmente 60 veces por segundo)

    void Update()

    {

        // Llama al método ActualizarInterfaz en cada frame para mantener la UI actualizada

        ActualizarInterfaz();

    }

    // Método personalizado que actualiza los elementos visuales de la interfaz

    void ActualizarInterfaz()

    {

        // Calcula el porcentaje de salud (entre 0 y 1) y lo asigna al fillAmount de la barra

        // Si Salud=50 y SaludMaxima=100, fillAmount será 0.5 (50% de la barra llena)

        BarraSalud.fillAmount = Salud / SaludMaxima;

        // Actualiza el texto mostrando “Salud: ” seguido del valor de Salud formateado sin decimales

        // ToString(“f0”) formatea el número float a string sin decimales (ejemplo: 75.8 se muestra como “76”)

        TextoSalud.text = “Salud: ” + Salud.ToString(“f0”);

    }

}

Como siguiente paso, le añadiremos un sistema de partículas para cuando golpean al jugador, pueden crear un sistema de partículas desde:

ahí ya ajustan la duración, el tamaño el tipo de partículas y todo lo necesario para crear un mejor efecto visual:

Después de hacer el sistema de partículas  y tener ya nuestro fuego, vamos a hacer que el jugador reciba daño, esto es más como prueba para poder como funciona la barra de vida.
Como primero agregamos un colider marcado como Trigger, para que el jugador pueda interactuar sin colisionar físicamente. Se crea un script que, al detectar que el objeto con tag “Player” ingresó al trigger, le aplica daño mediante el método RecibirDaño agregado previamente dentro del script de salud.El script es el siguiente:
using System.Collections;

using System.Collections.Generic;

using UnityEngine;

// Script que se coloca en objetos que causan daño (enemigos, trampas, fuego, proyectiles, etc.)

public class PersonaJe : MonoBehaviour

{

    // Variable pública que define cuánto daño causa este objeto

    // Se puede ajustar desde el Inspector de Unity

    public float CantidadDaño;

    // Método que se ejecuta automáticamente cuando otro objeto CON COLLIDER entra en el trigger de este objeto

    // IMPORTANTE: Este objeto debe tener un Collider con “Is Trigger” activado

    private void OnTriggerEnter(Collider other)

    {

        // Verifica dos condiciones:

        // 1. El objeto que entró tiene el tag “Player”

        // 2. El objeto que entró tiene el componente/script Codigo_Salud adjunto

        if(other.CompareTag(“Player”) && other.GetComponent<Codigo_Salud>())

        {

            // Si ambas condiciones son verdaderas:

            // Obtiene el componente Codigo_Salud del jugador y llama a su método RecibirDaño

            // Le pasa como parámetro la cantidad de daño configurada en CantidadDaño

            other.GetComponent<Codigo_Salud>().RecibirDaño(CantidadDaño);

        }

    }

}
También debemos agregar una nueva linea de codigo en nuestro script de salud de la siguiente forma:

la línea es la siguiente:
public void RecibirDaño(float daño)

{

    Salud -= daño;

}

Después, se crea un nuevo Canvas que funcionará como pantalla de muerte. Esta pantalla se ajusta para cubrir completamente la vista y se agrega un texto o mensaje indicando que el jugador ha muerto.

La ajustamos para que cubra toda la pantalla.

Ponemos un texto y una cámara para que no nos moleste más adelante.

arrastramos despues el canvas a los assets, y actualizamos el código de salud a lo siguiente:
[Header(“Muerte”)]

public GameObject Muerto;

if(Salud <= 0)

{

    Instantiate(Muerto);

    Destroy(gameObject);

}

y lo añadimos en la siguiente parte.

Esto nos soltara el canvas de estas muerto cuando la vida baje a 0.

Vamos a agregar este efecto de daño cuando sabes que te queda poca salud, se implementa un efecto de mancha de sangre en la pantalla cuando el jugador recibe daño. Se crea una imagen roja semitransparente y se añade un Canvas Group que permite controlar la opacidad de esa imagen. Luego se actualiza el código para que cuando el jugador reciba daño, la huella aparezca con opacidad total y luego se desvanezca gradualmente usando Time.deltaTime.

public CanvasGroup huelladesangre;


Salud -= daño;

        huelladesangre.alpha = 1;


if(huelladesangre.alpha > 0)

        {

            huelladesangre.alpha -= Time.deltaTime;

        }

ya con estas líneas de código cuando el jugador recibe daño se verá la huella en la pantalla que va desapareciendo con el tiempo.

Añadir un timer:

Para implementar un timer dentro del videojuego, se añade un Canvas y un texto dentro de la escena para crear un contador de tiempo. Este contador puede funcionar como cronómetro ascendente o como un temporizador regresivo, según el valor inicial del tiempo.

Añadimos un canvas y un texto.

Y se crea también un objeto vacío y un script y se añade el siguiente codigo:
using UnityEngine;

using UnityEngine.UI;

public class Timer : MonoBehaviour

{

    public float timer = 0f;       // Si es 0 = cronómetro, si es >0 = cuenta regresiva

    public Text textoTimer;

    private bool isCountdown;      // Para detectar si debe contar regresivo o progresivo

    void Start()

    {

        // Si el valor inicial es mayor a cero → cuenta regresiva

        isCountdown = timer > 0;

    }

    void Update()

    {

        if (isCountdown)

        {

            // Cuenta regresiva

            timer -= Time.deltaTime;

            // Evita que llegue a negativo

            if (timer < 0)

                timer = 0;

        }

        else

        {

            // Cronómetro (cuenta hacia arriba)

            timer += Time.deltaTime;

        }

        // Convertir a minutos y segundos

        int minutos = Mathf.FloorToInt(timer / 60);

        int segundos = Mathf.FloorToInt(timer % 60);

        // Mostrar en formato MM:SS

        textoTimer.text = minutos.ToString(“00”) + “:” + segundos.ToString(“00”);

    }

}

Después de guardar en el objeto vacío ponemos nuestro código y ponemos el objeto text donde se nos pide en inspector importante que esté vacío y ajustar el tiempo para lo que necesitemos y listo, tenemos nuestro contador de tiempo puesto.

así es como se vería en el juego.

Objetos Recogibles:

Se sube un objeto al proyecto, en este caso una gema generada desde Blender y se le añade un collider tipo caja que se marca con Is Trigger.

Nos vamos a tag, y se crea un nuevo tag para identificar ese objeto como recogible. Se desarrolla un script que detecta cuando el jugador lo toca, recupera un porcentaje de vida, suma al contador de diamantes y destruye el objeto recogido.

using UnityEngine;

public class Diamante : MonoBehaviour

{

    public float vidaARecuperar = 20f;

    private void OnTriggerEnter(Collider other)

    {

        if (other.CompareTag(“Player”))

        {

            // Recuperar vida

            var salud = other.GetComponent<Codigo_Salud>();

            if (salud != null)

            {

                salud.Salud += vidaARecuperar;

                if (salud.Salud > salud.SaludMaxima)

                    salud.Salud = salud.SaludMaxima;

            }

            // SUMAR al contador de diamantes

            ContadorDiamantes.instancia.AgregarDiamante(1);

            // Destruir diamante

            Destroy(gameObject);

        }

    }

}

Con este script también hacemos que recupere vida el personaje.

Creamos un prefabricado arrastrando el objeto a la carpeta de assets y borramos el objetos de la jerarquía.
Después añadimos un nuevo script.

using UnityEngine;

public class DiamondSpawner : MonoBehaviour

{

    [Header(“Configuración”)]

    public GameObject diamantePrefab;       // Prefab del diamante

    public int cantidadDiamantes = 20;      // Cuántos spawnnear

    public float rangoSpawn = 50f;          // Radio de spawn

    public float alturaSpawn = 1f;          // Altura del diamante

    [Header(“Spawn en NavMesh”)]

    public bool usarNavMesh = true;

    void Start()

    {

        SpawnearDiamantes();

    }

    void SpawnearDiamantes()

    {

        int diamantesSpawneados = 0;

        int intentos = 0;

        int maxIntentos = cantidadDiamantes * 10;

        while (diamantesSpawneados < cantidadDiamantes && intentos < maxIntentos)

        {

            intentos++;

            // Generar posición aleatoria en el rango

            Vector3 posicionAleatoria = transform.position + Random.insideUnitSphere * rangoSpawn;

            posicionAleatoria.y = alturaSpawn;

            // Verificar NavMesh

            if (usarNavMesh)

            {

                UnityEngine.AI.NavMeshHit hit;

                if (UnityEngine.AI.NavMesh.SamplePosition(posicionAleatoria, out hit, rangoSpawn, UnityEngine.AI.NavMesh.AllAreas))

                {

                    posicionAleatoria = hit.position;

                    posicionAleatoria.y = alturaSpawn;

                    // Instanciar diamante

                    Instantiate(diamantePrefab, posicionAleatoria, Quaternion.identity);

                    diamantesSpawneados++;

                }

            }

            else

            {

                Instantiate(diamantePrefab, posicionAleatoria, Quaternion.identity);

                diamantesSpawneados++;

            }

        }

        Debug.Log(“Diamantes spawneados: ” + diamantesSpawneados);

    }

    // Visualizar rango en el editor

    void OnDrawGizmosSelected()

    {

        Gizmos.color = Color.cyan;

        Gizmos.DrawWireSphere(transform.position, rangoSpawn);

    }

}

este nos permitirá crear los cristales en el mapa de manera aleatoria

Se añade un nuevo objeto vacío y arrastramos el prefab, escogemos la cantidad de diamantes y la distancia que tendrán del suelo.

Se crea un objeto vacío con un canvas y un texto y después creamos un script el cual nos servirá como contador.

añadimos el texto al objeto vacío y como siempre ponemos el texto y después definimos la cantidad de diamantes que hay y ya cada vez que recojamos un diamante el contador subirá a 1.

Tutorial:

Empezamos creando un objeto vacío que tenga un Canvas y un texto. Seguido un script que agregue la funcionalidad de mostrar todo el tutorial en un texto.

Menú principal:

En la creación del menú principal, lo primero es generar un nuevo Canvas, que será el contenedor de toda la interfaz que verá el jugador antes de iniciar la partida. Se debe ajustar el Canvas en el modo Scale With Screen Size, ya que esto garantiza que todos los elementos del menú se ajusten automáticamente a cualquier resolución o tamaño de pantalla.

Una vez creado el Canvas el cual funcionará como la base visual del menú. Este panel suele cubrir toda la pantalla y puede tener un color sólido, una textura o una imagen de fondo que sirva como estética principal del menú. 

Seguido de esto se agrega una imagen adicional si queremos reforzar el estilo visual, por ejemplo un logo, una ilustración o un marco decorativo para la interfaz.

Seguido a esto, se agrega un TextMeshPro, que será el encargado de mostrar el título del juego o los textos principales del menú. TextMeshPro es preferible frente al texto estándar debido a su mayor nitidez, calidad visual y personalización.

Luego se añade un botón dentro del Canvas. Este botón puede ser creado desde la opción UI → Button (TextMeshPro) y se ajusta su tamaño, su posición y su estilo visual para que encaje correctamente dentro del diseño general.

Una forma para mantener el proyecto organizado, creando un GameObject vacío dentro del Canvas, el cual funcionará como contenedor o grupo para todos los botones del menú, permitiendo administrar fácilmente la interfaz, mover todos los botones juntos mantener una estructura limpia.

Una vez que tenemos configurado el primer botón con su estilo visual y su tamaño, lo duplicamos cada botón duplicado se renombra según su función y se posiciona dentro del contenedor.

Ahora, ya con los botones ajustados, se crean los paneles que formarán parte del menú de opciones, los cuales pueden incluir configuraciones como, ajustes de volumen, configuración de sensibilidad, resolución de pantalla, información del juego y controles. Para ello, agregamos nuevos paneles dentro del Canvas y los acomodamos para que funcionen como submenús. Cada panel se activa o desactiva dependiendo del botón presionado.

añadimos el siguiente codigo:
using UnityEngine;

using UnityEngine.SceneManagement;

public class MainMenu : MonoBehaviour

{

    public GameObject optionsMenu;

    public GameObject mainMenu;

    public void OpenOptionsPanel()

    {

        mainMenu.SetActive(false);

        optionsMenu.SetActive(true);

    }

    public void OpenMainMenuPanel()

    {

        mainMenu.SetActive(true);

        optionsMenu.SetActive(false);

    }

    public void QuitGame()

    {

        Application.Quit();

    }

    public void PlayGame()

    {

        SceneManager.LoadScene(“PlayerS”);

    }

}Este script es el encargado de controlar el comportamiento del menú principal. Gracias a él podemos cambiar entre el panel principal y el panel de opciones, iniciar el juego y cerrar la aplicación.

Después de escribir el código, se arrastra el script hacia el Canvas para que Unity reconozca las funciones y permita vincularlas desde el Inspector.

Seleccionamos cada botón del menú y desde su componente Button → OnClick(), elegimos la función del script que debe ejecutarse.

Video explicación

CREDITOS:

Autores: Nicolás Araújo Rodríguez, Nicolás Cruz Capella

Editor: Carlos Iván Pinzón Romero

Código: UCMV

Universidad: Universidad Central

REFERENCIAS

LuisCanary [@LuisCanary]. (s/f-a). Como hacer un TIMER en Unity 🕒💯 [[Object Object]]. Youtube. Recuperado el 26 de noviembre de 2025, de https://www.youtube.com/watch?v=dgMImeoZG5w
LuisCanary [@LuisCanary]. (s/f-b). Como Recoger ITEMS y Objetos en Unity (OnTriggerEnter) 🤚🍩🍰 [[Object Object]]. Youtube. Recuperado el 26 de noviembre de 2025, de https://www.youtube.com/watch?v=-4GDzNz2t8M
Myinstants. (s/f-a). Mii Channel Music. myinstants. Recuperado el 26 de noviembre de 2025, de https://www.myinstants.com/es/instant/mii-channel-music-82732/?utm_source=copy&utm_medium=share

Myinstants. (s/f-b). Myinstants - Sonidos divertidos populares de Colombia. myinstants. Recuperado el 26 de noviembre de 2025, de https://www.myinstants.com/es/index/co/
Myinstants. (s/f-c). Nemesis Resident Evil 3 ROAR. myinstants. Recuperado el 26 de noviembre de 2025, de https://www.myinstants.com/es/instant/nemesis-resident-evil-3-roar/?utm_source=copy&utm_medium=share
Myinstants. (s/f-d). Welcome Mercador RE4. myinstants. Recuperado el 26 de noviembre de 2025, de https://www.myinstants.com/es/instant/welcome-mercador-re4-20742/?utm_source=copy&utm_medium=share
Ritch, D. [@devritch]. (s/f-a). Como hacer que un enemigo te ataque en Unity (Enemigo que te hace daño) || Tutorial Unity 2025 [[Object Object]]. Youtube. Recuperado el 26 de noviembre de 2025, de https://www.youtube.com/watch?v=cPtxfGPNeEc
Ritch, D. [@devritch]. (s/f-b). Como hacer un enemigo que te persiga con RANGO en Unity || Enemigo te persigue || Tutorial 2025 [[Object Object]]. Youtube. Recuperado el 26 de noviembre de 2025, de https://www.youtube.com/watch?v=-G3x0M2EynM
Ritch, D. [@devritch]. (s/f-c). Como hacer una barra de vida y que el jugador reciba daño || Tutorial Unity 2025 [[Object Object]]. Youtube. Recuperado el 26 de noviembre de 2025, de https://www.youtube.com/watch?v=jafu7eAjpuk