2. Supercargando el Juego

Hoy vamos a Supercargar nuestro “Primer Juego 3D” en Unity 🙂

Para ser más precisos, vamos a mejorar nuestro código “Player Controller” para añadirle “Habilidades Especiales” a nuestro personaje como “Dash“, “Doble Salto“, “Correr“, “Planear“, “Correr por los Muros” y “Salto en Muro“.

Tutorial de Unity Nivel: Intermedio.
“Publicidad” (los Anuncios nos Ayudan a Mantener este “Gran Sitio” 😀 )

2.1 Cambiando el Código.

Alerta de Spoiler “Se viene muchísimo Código” 🙁

Con la intención de añadirle a nuestro personaje las habilidades de “doble salto”, “correr”, “correr y saltar por los muros”, “planear” y hacer “dash”(además de mejorar los movimientos básicos); vamos a “modificar completamente” nuestro código “PlayerController” para que quede como se muestra a continuación (hemos añadido todos los comentarios posibles para que ayuden a explicar que es lo que hace cada parte). Así que, comencemos:

using UnityEngine;
using UnityEngine.InputSystem;

[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
    // =================================================
    // VARIABLES EDITABLES EN EL INSPECTOR
    // =================================================
    
    [Header("General Config")]
    public float moveSpeed = 5f;           // Velocidad base de movimiento
    public float jumpForce = 5f;           // Fuerza del salto normal
    public float risingGravity = -15f;     // Fuerza de gravedad al saltar 
    public float fallingGravity = -25f;    // Fuerza de gravedad al caer
    public Transform cameraTransform;      // Transform de la cámara principal
    public float groundCheckRadius = 0.4f; // Radio para detección de suelo
    
    [Header("Components")]
    public Transform groundCheck;          // Objeto para detectar suelo
    public LayerMask groundMask;           // Capas consideradas como suelo

    [Header("Abilities")]
    public bool canDoubleJump = false;     // Doble salto
    public bool canDash = false;           // Dash o movimiento rápido
    public bool canWallJump = false;       // Salto en pared
    public bool canWallRun = false;        // Correr en paredes
    public bool canGlide = false;          // Planear
    public bool canSprint = false;         // Sprint o Carrera acelerada

    [Header("Abilities Values")]
    public float sprintSpeed = 10f;         // Velocidad durante el sprint
    public float dashSpeed = 30f;           // Velocidad del dash
    public float dashDuration = 0.15f;      // Duración del dash
    public float dashCooldown = 0.75f;      // Tiempo entre dashes
    public bool allowAirDash = true;        // ¿Permitir dash en el aire?
    public float wallJumpForce = 15f;       // Fuerza del salto en pared
    public float wallRunGravity = 1f;       // Gravedad reducida en wall run
    public float wallRunSpeed = 11f;        // Velocidad durante wall run
    public float glideFallSpeed = -1.75f;   // Velocidad de caída al planear

    // =================================================
    // VARIABLES PRIVADAS
    // =================================================
    
    private CharacterController controller; // Componente CharacterController
    private Vector3 velocity;               // Velocidad actual del jugador
    private bool isGrounded;                // ¿Está tocando el suelo?
    private int jumpCount = 0;              // Contador para doble salto
    private bool isDashing = false;         // ¿Actualmente en dash?
    private float dashTimer = 0f;           // Temporizador para el dash
    private bool isWallRunning = false;     // ¿Actualmente en wall run?
    private bool isGliding = false;         // ¿Actualmente planeando?
    private Vector3 wallNormal;             // Normal de la pared actual
    private bool isTouchingWall = false;    // ¿Tocando una pared?
    private float lastDashTime = -10f;      // Tiempo del último dash
    private bool hasAirDashed = false;      // ¿Ya se usó dash en el aire?


    // =================================================
    // INICIALIZACIÓN
    // =================================================
    
    void Start()
    {
        //Asignamos el componente Character Controller a una variable para poder manipularlo
        controller = GetComponent<CharacterController>();
        //Hacemos que el cursor sea invisible
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
    }

    // =================================================
    // BUCLE PRINCIPAL
    // =================================================
    
    void Update()
    {
        // Verificar estados básicos
        CheckGround();
        CheckWalls();

        // Manejar habilidades
        HandleMovement();
        HandleJump();
        HandleDash();
        HandleWallRun();
        HandleGlide();
    }

    // =================================================
    // MÉTODOS DE DETECCIÓN
    // =================================================
    
    
    /// Verifica si el jugador está tocando el suelo    
    private void CheckGround()
    {
        // Detectar colisión con el suelo usando una esfera física
        // - Parámetros:
        //   * Posición: Usa la posición del objeto groundCheck (pies del personaje)
        //   * Radio: groundCheckRadius (tamaño de la esfera de detección)
        //   * Capa: groundMask (solo detecta colisiones con capas definidas como suelo)
        isGrounded = Physics.CheckSphere(groundCheck.position, groundCheckRadius, groundMask);

        // Si está en el suelo, resetear variables de estado
        if (isGrounded)
        {
            // Resetear contador de saltos (permite nuevo salto básico)
            jumpCount = 0;
            // Reactivar dash aéreo (permite usar dash en el próximo salto)
            hasAirDashed = false;
            // Resetear velocidad horizontal solo si NO está en dash
            // - Evita interrumpir el impulso del dash
            // - Mantiene momentum si está dashando en el suelo
            if(!isDashing){
                velocity.x = 0; // Detener movimiento en eje X
                velocity.z = 0; // Detener movimiento en eje Z
            }
            
        }
    }

    
    /// Detecta paredes cercanas en 4 direcciones principales y almacena su normal usando SphereCast    
    private void CheckWalls()
    {
         isTouchingWall = false; // Inicializar estado de contacto con pared
        float detectionDistance = 1.2f; // Configurar parámetros de detección
        
        // Direcciones a verificar (frontal, derecha, izquierda, trasera)
        Vector3[] directions = new Vector3[]
        {
            transform.forward, // Dirección frontal (Z positivo)
            transform.right,   // Dirección derecha (X positivo)
            -transform.right,  // Dirección izquierda (X negativo)
            -transform.forward // Dirección trasera (Z negativo)
        };

         // Recorrer todas las direcciones definidas
        RaycastHit hit;
        foreach(Vector3 dir in directions)
        {
            // Realizar SphereCast en dirección actual
            if(Physics.SphereCast(
                transform.position, // Origen en centro del personaje
                0.5f,               // Radio de la esfera (mitad del ancho del personaje)
                dir,                // Dirección actual del chequeo
                out hit,            // Información de la colisión
                detectionDistance,  // Distancia máxima de detección
                groundMask          // Solo detectar capas definidas como suelo/pared
            ) && Vector3.Angle(hit.normal, Vector3.up) > 50f) // Excluir pisos
            // Filtrar superficies planas (pisos/techos)
            // - Calcula ángulo entre la normal de la superficie y el eje vertical
            // - Si >50°, se considera pared (evita detectar pisos inclinados)
            {
                isTouchingWall = true; // Indicar contacto con pared
                wallNormal = hit.normal; // Almacenar normal de la pared
                return; // Salir del método tras primera detección válida
            }
        }
    }

    // =================================================
    // SISTEMA DE MOVIMIENTO
    // =================================================
    
    
    /// Maneja el movimiento básico y el sprint    
    private void HandleMovement()
    {
        if (isDashing) return; // Ignorar movimiento normal durante el dash

        // Obtener inputs de ejes horizontales/verticales (teclas WASD o joystick y Valores entre [-1, 1])
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");
        
        // Calcular dirección de movimiento relativa a la cámara
        // - Convierte inputs en vector 3D orientado según la rotación de la cámara
        Vector3 moveDirection = CalculateCameraMovement(horizontal, vertical);
        
        // Determinar velocidad actual considerando sprint
        // - Usa sprintSpeed si se mantiene el botón de sprint y está habilitado
        // - Mantiene moveSpeed como valor base en otros casos
        float currentSpeed = canSprint && Input.GetButton("Sprint") ? sprintSpeed : moveSpeed;

         // Aplicar movimiento horizontal solo si no está en wall run
        // - Evita conflicto entre sistemas de movimiento
        if(!isWallRunning)
        {
            // Mueve el CharacterController en la dirección calculada
            // - Multiplica por deltaTime para hacerlo frame-rate independent
            controller.Move(moveDirection * currentSpeed * Time.deltaTime);
        }

        // Calcular gravedad según estado (ascenso/descenso)
        // - fallingGravity: Gravedad fuerte durante caída o en el aire
        // - risingGravity: Gravedad suave durante ascenso
        float currentGravity = (velocity.y < 0 || !isGrounded) ? fallingGravity : risingGravity ;

        // Aplicar gravedad solo si no está en wall run/glide
        if(!isWallRunning && !isGliding)
        {
            velocity.y += currentGravity * Time.deltaTime;
        }
        
        // Aplicar velocidad vertical acumulada
        // - Movimiento independiente del horizontal para mejor control
        controller.Move(velocity * Time.deltaTime);
    }

    
    /// Calcula dirección de movimiento relativa a la cámara    
    private Vector3 CalculateCameraMovement(float horizontal, float vertical)
    {
        // Obtener vectores direccionales base de la cámara
        Vector3 cameraForward = cameraTransform.forward; // Vector hacia donde apunta la cámara
        Vector3 cameraRight = cameraTransform.right; // Vector derecho de la cámara
        
        // Aplanar vectores para ignorar componente vertical (eje Y)
        // - Evita movimiento no deseado al mirar hacia arriba/abajo
        // Mantener solo componentes X y Z
        cameraForward.y = 0;
        cameraRight.y = 0;
        
        // Normalizar vectores para mantener velocidad constante
        // - Evita que movimientos diagonales sean más rápidos
        cameraForward.Normalize(); // Longitud = 1
        cameraRight.Normalize();

        // Combinar direcciones según input del jugador
        // - Vertical: Controla movimiento adelante/atrás (eje Z)
        // - Horizontal: Controla movimiento lateral (eje X)
        Vector3 combinedDirection = (cameraForward * vertical) + (cameraRight * horizontal);

        // Normalizar resultado final para:
        // - Mantener velocidad constante en todas las direcciones
        // - Evitar valores mayores a 1 en movimiento diagonal
        return combinedDirection.normalized;
    }

    // =================================================
    // SISTEMA DE SALTO
    // =================================================
    
    
    /// Maneja todos los tipos de salto    
    private void HandleJump()
    {
        // Detectar que el botón de Salto ha sido oprimido
        if(Input.GetButtonDown("Jump"))
        {
            isGliding = false; // Cancelar cualquier estado de planeo activo

            // Salto normal/doble salto
            if(isGrounded || (canDoubleJump && jumpCount < 1))
            {
                // Fórmula física para altura de salto: v = √(2 * fuerza * gravedad)
                // - risingGravity se usa para cálculo consistente con el sistema de gravedad
                velocity.y = Mathf.Sqrt(jumpForce * -2 * risingGravity);
                jumpCount++; // Incrementar contador de saltos aéreos
            }
            // Wall jump
            else if(canWallJump && isTouchingWall)
            {
                // Calcular dirección óptima (alejarse de la pared + vertical)
                // - Vector.up * 2: Impulso vertical predominante
                // - wallNormal: Dirección perpendicular a la pared para empuje horizontal
                Vector3 jumpDirection = (Vector3.up * 2f + wallNormal).normalized;
                
                // Aplicar fuerza con impulso
                velocity = jumpDirection * wallJumpForce;
                
                // Resetear estados
                jumpCount = 0;
                hasAirDashed = false; // Reactivar dash aéreo
                
                // Rotar personaje para mirar en dirección opuesta a la pared
                // - LookRotation(-wallNormal): Crea rotación alejándose de la pared
                transform.rotation = Quaternion.LookRotation(-wallNormal);
            }
        }

    }

    // =================================================
    // SISTEMA DE DASH
    // =================================================
    
    
    /// Maneja el movimiento de dash    
    private void HandleDash()
    {
        // ¿Puede dashar en el aire? (Habilidad activa + en aire + no ha usado dash aéreo)
        bool canAirDash = allowAirDash && !isGrounded && !hasAirDashed;
        // ¿Puede dashar en suelo? (En suelo + cooldown completado)
        bool canGroundDash = isGrounded && Time.time >= lastDashTime + dashCooldown;
        
        // Iniciar Dash si se oprime el botón y se cumplen las condiciones
        if (canDash && Input.GetButtonDown("Fire2") && (canGroundDash || canAirDash))
        {
            // Configurar variables
            isDashing = true; // Activar flag de dash
            dashTimer = dashDuration; // Iniciar temporizador
            lastDashTime = Time.time; // Registrar momento del último dash
            
            // Registrar dash aéreo (solo una vez por salto)
            if(!isGrounded) hasAirDashed = true;

            // Calcular dirección del dash
            Vector3 dashDirection = GetDashDirection();

            // Aplicar fuerza del dash (combinar con velocidad vertical existente)
            velocity = dashDirection * dashSpeed; // Velocidad en dirección calculada
            velocity.y = isGrounded ? 0 : velocity.y; // En suelo: dash puramente horizontal
        }

        // Ejecutar lógica durante el dash
        if (isDashing)
        {
            dashTimer -= Time.deltaTime; // Reducir temporizador

            // Finalizar dash cuando el tiempo expire
            if (dashTimer <= 0)
            {
                isDashing = false;
                velocity = Vector3.zero; // Eliminar velocidad residual
            }

            //Aplicar movimiento del dash (Movimiento frame-rate independent)
            controller.Move(velocity * Time.deltaTime);
        }
    }

    // Calcula la dirección del dash basada en el input y la cámara
    private Vector3 GetDashDirection()
    {
        // Obtener input RAW (sin suavizado de Unity) para dirección inmediata
        float horizontal = Input.GetAxisRaw("Horizontal"); // Valores: -1, 0, 1
        float vertical = Input.GetAxisRaw("Vertical"); // Valores: -1, 0, 1
        
        // Calcular dirección relativa a la cámara (igual que el movimiento normal)
        Vector3 cameraForward = cameraTransform.forward;
        Vector3 cameraRight = cameraTransform.right;
        // Ignorar componente vertical para movimiento horizontal
        cameraForward.y = 0f;
        cameraRight.y = 0f;
        // Normalizar vectores para mantener velocidad constante
        cameraForward.Normalize();
        cameraRight.Normalize();

        // Combinar direcciones según input y normalizar para evitar velocidad extra en diagonales
        Vector3 dashDirection = (cameraForward * vertical + cameraRight * horizontal).normalized;

        // Si no hay input, usar dirección frontal de la cámara
        if(dashDirection == Vector3.zero)
        {
            dashDirection = cameraForward; // Dash hacia adelante por defecto
        }

        return dashDirection;

    }



    // =================================================
    // SISTEMA DE WALL RUN
    // =================================================
    
    
    /// Maneja el movimiento en paredes    
    private void HandleWallRun()
    {   
        // Iniciar el wall run si se oprime el botón correspondiente y se cumplen las condiciones
        if(canWallRun && !isGrounded && isTouchingWall && Input.GetButton("Sprint"))
        {
            isWallRunning = true;

            // Rotación automática hacia la pared:
            // - Interpola suavemente la rotación actual hacia la dirección opuesta a la normal de la pared
            // - Quaternion.LookRotation(-wallNormal): Crea rotación mirando en dirección contraria a la pared
            // - 10f * Time.deltaTime: Velocidad de rotación (10 grados/segundo)
            transform.rotation = Quaternion.Lerp(
            transform.rotation,
            Quaternion.LookRotation(-wallNormal),
            10f * Time.deltaTime
            );
            
            // Calcular direcciones de movimiento:
            // Dirección "frontal" paralela a la pared (90° respecto a la normal)
            Vector3 wallForward = Vector3.Cross(wallNormal, Vector3.up);
            // Proyectar dirección de la cámara sobre el plano de la pared
            // - Vector3.ProjectOnPlane: Elimina componente perpendicular a la pared
            // - .normalized: Mantiene velocidad constante
            Vector3 cameraRelative = Vector3.ProjectOnPlane(cameraTransform.forward,wallNormal).normalized;
            
            // Mover en dirección combinada (pared + input)
            float horizontal = Input.GetAxis("Horizontal");
            float vertical = Input.GetAxis("Vertical");
            // Combinar direcciones:
            // - vertical * cameraRelative: Movimiento hacia adelante/atrás relativo a la cámara
            // - horizontal * transform.right: Movimiento lateral relativo a la rotación del personaje
            Vector3 moveDirection = (cameraRelative * vertical + transform.right * horizontal) * wallRunSpeed;
            
            // Aplicar movimiento:
            controller.Move(moveDirection * Time.deltaTime); // Movimiento horizontal
            // Configurar gravedad vertical personalizada:
            // - Valor negativo para mantener al personaje "pegado" a la pared
            // - wallRunGravity controla la velocidad de deslizamiento
            velocity.y = -wallRunGravity;
        }
        else
        {
            isWallRunning = false; // Desactivar wall run si no se cumplen las condiciones
        }
    }

    // =================================================
    // SISTEMA DE PLANEO
    // =================================================
    
    
    /// Maneja el planeo aéreo    
    private void HandleGlide()
    {
        //Iniciar el planeo si se mantiene oprimido el botón correspondiente y se cumplen las condiciones requeridas
        if (canGlide && !isGrounded && Input.GetButton("Jump") && !isGliding && velocity.y<0)
        {
            isGliding = true;
            velocity.y = glideFallSpeed; //Se modifica la velocidad en el eje vertical para usar el valor definido
        }
    // Cancelar planeo al tocar el suelo o soltar el botón
    else if (isGrounded || Input.GetButtonUp("Jump"))
        {
            isGliding = false;
        }
    }
}

 

“Publicidad” (los Anuncios nos Ayudan a Mantener este “Gran Sitio” 😀 )

2.2 Configurando Controles

Como pudiste ver en la sección anterior, las habilidades especiales se activan al oprimir botones específicos:

Botón “Jump” → Activa “Saltar, Doble Salto, Salto en Muro y Planear” → Oprimiendo “Barra Espaciadora” (en teclado) o “Botón Y” (en gamepad).

Botón “Sprint” → Activa “Correr y Correr en Muro” → Oprimiendo “Shift Izquierdo” (en teclado) o “Botón del Joystick Izquierdo” (en gamepad).

Botón “Fire2” → Activa “Dash” → Oprimiendo “Alt Izquierdo” (en teclado) o “Botón B” (en gamepad).

Tanto “Jump” como “Fire2” ya están previamente configurados en Unity 6. A continuación veremos como configurar “Sprint“:

Primero, vamos a abrir los “Project Settings” de Unity (Edit → Project Settings).

Dentro de “Project Settings” hacemos clic en “Input Manager“. Dentro de “Input Manager” buscamos el parámetro “Size” y cambiamos el valor actual para añadirle “dos unidades más“, es decir, si actualmente el valor es “30“, lo vamos a cambiar a “32“. Esto creará dos elementos más al final de la lista los cuales serán usados para configurar “Sprint“.

A los dos elementos los vamos a llamar “Sprint” y cada uno tendrá la siguiente configuración:

 

“Publicidad” (los Anuncios nos Ayudan a Mantener este “Gran Sitio” 😀 )

2.3 Últimos ajustes.

Antes de probar nuestro juego es necesario que asignes los componentes y capas requeridas en el componente de “PlayerController” como se hizo en el tutorial anterior y, también es necesario que añadas más obstáculos de distintos tamaños que te permitan hacer uso de las nuevas habilidades. Así que, “Hazlo Yá” 😀

Por cierto, no olvides asignarles a los nuevos obstáculos la capa “ground3D” para que el juego los pueda reconocer como objetos en los cuales se activen las habilidades especiales.

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

Ejercicios.

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

  1. Modifica valores en el componente “Player Controller” para que tu personaje se mueva más rápido, salte más alto y caiga más lento al planear.
  2. Modifica valores en los componentes “Cinemachine Orbital Follow” y “Cinemachine Rotation Composer” que se encuentran en el objeto “FreeLook Camera” para que experimentes con distintos enfoques de tu cámara en tercera persona.
  3. Crea una nueva escena con muchos tipos de obstáculos para que puedas probar todas las habilidades como en “Los Juegos de Plataformas“.
  4. Crea más elementos dentro del “Input Manager” para modificar los controles del juego (por ejemplo, que puedas planear usando otro botón diferente al del salto).

Este Tutorial de Unity termina aquí. Esperamos que te haya sido de mucha utilidad y que hayas creado “Tu Primer Juego 3D”.

“Publicidad” (los Anuncios nos Ayudan a Mantener este “Gran Sitio” 😀 )

Unity Tutorial: “Tu Primer Juego 3D


1. Creando un Juego 3D

2. Supercargando el Juego

Ver más Tutoriales

 

“Ads”

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

Comparte el Post
Posted in PrimerJuego3D, Tutoriales, Unity.

Deja un comentario

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.