
Desarrollo de Personajes 3D y Sistema de IA para Videojuegos con Unity y Blender
Introducción
Una guía para el desarrollo de personajes 3D animados y la implementación de sistemas de inteligencia artificial para videojuegos. El proceso abarca desde la creación y personalización de personajes utilizando las plataformas Mixamo y Blender, hasta la programación de comportamientos de IA avanzados en Unity.
La guía detalla el flujo de trabajo completo que incluye la selección e importación de modelos 3D desde Mixamo, la personalización de personajes mediante el intercambio de cabezas utilizando Facebuilder en Blender, y la configuración de sistemas de animación complejos en Unity. Además, se profundiza en la implementación de un sistema de IA para enemigos zombie que utiliza el sistema de navegación NavMesh de Unity, permitiendo comportamientos realistas de persecución y ataque.
El documento cubre aspectos técnicos esenciales como la configuración del Animator Controller, la creación de sistemas de apuntado con IK (Inverse Kinematics), y la solución de problemas comunes relacionados con la física y navegación de personajes. Esta guía está dirigida a desarrolladores de videojuegos que buscan crear experiencias inmersivas con personajes inteligentes y sistemas de animación fluidos.
- Se realiza selección de los personajes en la plataforma mixamo

- Se procede a importar los personajes en la plataforma de Blender

- Se realiza la importación del facebuilder de los personajes

- Se retira la cabeza de los personajes para adaptar la nueva


- Se realiza adaptación de la nueva cabeza de los personajes en malla de blender

- Para el segundo personaje se realiza el mismo proceso, descargamos el personaje por la pagina de Mixamo

- Retiramos la cabeza e importamos la cabeza nueva

- Ajustamos la cabeza a la posición y el tamaño necesario

Configuración del Animator Controller
1: Abrir el Animator
- Logrará ver el Animator Controller en la ventana Animator
- En la ventana de Unity, ve a Window > Animation > Animator
- Selecciona tu personaje en la jerarquía
2: Crear parámetros
- En la pestaña “Parameters” (esquina superior izquierda), haz clic en el “+”
- Selecciona “Bool” y nómbralo “IsAiming”
- Haz clic nuevamente en “+” y selecciona “Trigger”, nómbralo “Shoot”
3: Configurar transiciones
- Arrastra tu animación de idle/movimiento normal al Animator (normalmente ya está)
- Arrastra tu animación de apuntado al Animator
- Haz clic derecho en la animación normal > “Make Transition” > arrastra a la animación de apuntado
- Selecciona la flecha de transición y en el inspector:
- En “Conditions” añade “IsAiming” = true
- Ajusta “Transition Duration” a ~0.1 para que sea suave
4: Activar IK Pass
- En el Animator, ve a la pestaña “Layers“
- Si no existe, crea una nueva capa con el “+” y nómbrala “UpperBody”
- Selecciona la capa y marca “IK Pass”
2. Crear el objeto AimTarget (personajes)
1: Crear el objeto vacío
- Haz clic derecho en tu personaje en la jerarquía
- Selecciona “Create Empty”
- Renombra el nuevo objeto como “AimTarget”
2: Posicionar el objeto (personajes)
- Selecciona “AimTarget”
- En el inspector (Transform), colócalo a unos metros delante del personaje:
- Position Z: 3-5 (dependiendo de tu escena)
- Position Y: ~1.5 (altura del brazo)
3. Configurar la capa UpperBody
1: Añadir animación a la capa
- En el Animator, selecciona la capa “UpperBody”
- Arrastra tu animación de apuntado/disparo a esta capa
2: Configurar peso
- En la pestaña Layers, verás un slider “Weight” para la capa UpperBody
- Pon este valor a 1 (para que se tenga prioridad sobre las animaciones base)
3: Ajustar máscara (opcional – Personajes)
- Si solo quieres que afecte a la parte superior del cuerpo:
- Haz clic en el engranaje de la capa
- En “Avatar Mask”, crea o selecciona una máscara que solo incluya torso y brazos
Configuración del Canvas para los movimientos del jugador.
Se selecciona el player, se pone la vista 2D y comenzamos con la configuración y los plugins.
Se crea un espacio vacio para agregar una nueva animación de arma
Hornear la NavMesh Usando el Componente NavMeshSurface
- ¿Qué es? El NavMeshSurface es el componente que se encarga de definir qué geometría debe ser usada para la NavMesh y de realizar el proceso de “baking”.
- Instrucciones:
- Crea un Objeto Vacío para la NavMesh (Recomendado):
- En la ventana “Hierarchy” (Jerarquía), haz clic derecho en un espacio vacío.
- Selecciona Create Empty (Crear Vacío).
- Renombra este nuevo GameObject (objeto de juego) a algo como “NavMeshBaker” o “LevelNavMesh”. Esto es útil para organizar tu escena.
- Agrega el Componente NavMeshSurface:
- Con tu nuevo objeto “NavMeshBaker” seleccionado en la “Hierarchy”.
- Ve a la ventana “Inspector” (normalmente a la derecha).
- Haz clic en el botón Añadir Componente.
- En la barra de búsqueda, escribe “NavMeshSurface”.
- Selecciónalo para agregarlo a tu objeto.
- Configura y Hornea la NavMesh:
- Una vez que el componente NavMeshSurface esté en tu objeto “NavMeshBaker”, verás sus propiedades en el Inspector.
Si todo está configurado correctamente, verás la capa azul de la NavMesh superpuesta en las áreas transitables de tu escenario en la vista “Scene”.
Escribir el Código del ZombieIA (personaje)
- ¿Qué es? Aquí es donde programaremos al zombie para que busque al jugador, lo persiga y lo ataque.
Instrucciones:
- Haz doble clic en el script “ZombieAI” en tu ventana “Project”. Esto abrirá Visual Studio (o el editor de código que tengas configurado con Unity).
Codigo:
using UnityEngine;
using UnityEngine.AI; // Necesitamos esto para usar NavMeshAgent
using- System.Collections; // Necesitamos esto para las corrutinas
public class ZombieAI : MonoBehaviour
{
public- Transform playerTransform; // Variable para almacenar la referencia al jugador
public float chaseRange = 10f; // Distancia a la que el zombie empezará a perseguir
public float attackRange = 1.5f; // Distancia a la que el zombie empezará a atacar
public- float attackCooldown = 2f; // Tiempo entre ataques
public int attackDamage = 10; // Daño que inflige el zombie por ataque
private NavMeshAgent agent; // Referencia al componente NavMeshAgent
private bool isAttacking = false; // Bandera para controlar si el zombie ya está atacando
void Start()
{
// Obtener la referencia al componente NavMeshAgent en este GameObject
agent = GetComponent<NavMeshAgent>();
// Intentar encontrar al jugador por su Tag, esto es crucial para que el zombie sepa a quién seguir.
GameObject playerObject = GameObject.FindWithTag(“Player”);
if (playerObject != null)
{
playerTransform = playerObject.transform;
}
else
{
Debug.LogError(“¡No se encontró el objeto con el Tag ‘Player’! Asegúrate de que tu jugador tiene el tag ‘Player’.”, this);
enabled = false; // Desactiva el script si no se encuentra al jugador
}
}
void Update()
{
if (playerTransform == null) return; // Si no hay jugador, no hacer nada
// Calcular la distancia entre el zombie y el jugador
float distanceToPlayer = Vector3.Distance(transform.position, playerTransform.position);
// Si el jugador está dentro del rango de persecución
if (distanceToPlayer <= chaseRange)
{
// Mover el agente hacia la posición del jugador
// Unity NavMeshAgent se encargará de la ruta inteligente
agent.SetDestination(playerTransform.position);
// Si el jugador está dentro del rango de ataque y no estamos atacando
if (distanceToPlayer <= attackRange && !isAttacking)
{
// Detener el movimiento del agente para atacar
agent.isStopped = true;
StartCoroutine(AttackRoutine()); // Iniciar la rutina de ataque
}
else if (distanceToPlayer > attackRange && agent.isStopped)
{
// Si el jugador se alejó del rango de ataque, reanudar la persecución
agent.isStopped = false;
}
}
else
{
// Si el jugador está fuera del rango de persecución, detener el movimiento
// Opcional: Podrías hacer que el zombie patrulle o esté ocioso aquí
if (agent.hasPath) // Solo si tiene una ruta activa
{
agent.isStopped = true;
}
}
IEnumerator AttackRoutine()
{
isAttacking = true; // Establecer bandera de ataque
Debug.Log(“Zombie atacando!”); // Mensaje de depuración
// TODO: Aquí pondrías la lógica de aplicar daño al jugador.
// Por ahora, solo lo imprimimos. Necesitarías un script de “Health” en el jugador.
//Ejemplo: PlayerHealth playerHealth = playerTransform.GetComponent<PlayerHealth>();
// if (playerHealth != null) { playerHealth.TakeDamage(attackDamage); }
// Esperar el tiempo de cooldown antes de poder atacar de nuevo
yield return new WaitForSeconds(attackCooldown);
isAttacking = false; // Resetear bandera de ataque
// Reanudar el movimiento del agente si el jugador sigue en rango y no se ha movido
if (Vector3.Distance(transform.position, playerTransform.position) <= attackRange)
{
// Mantenerse detenido si aún está en rango de ataque
agent.isStopped = true;
}
else
{
// Si el jugador se alejó, reanudar la persecución
agent.isStopped = false;
}
}
// Esta función dibuja las esferas de rango en el editor para que las veas
void OnDrawGizmosSelected()
{
// Rango de persecución (amarillo)
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, chaseRange);
// Rango de ataque (rojo)
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
}
}
Esto es un problema común y tiene varias causas posibles, especialmente cuando se trabaja con NavMeshAgent y la configuración inicial de un personaje.
Que el zombie “desaparezca” (generalmente significa que cae infinitamente o se teletransporta a un lugar inesperado) casi siempre está relacionado con cómo se está manejando su física o su posición inicial.
Vamos a depurar esto paso a paso.
Diagnóstico del Problema: Zombie Desaparece
Esto suele ocurrir por una de las siguientes razones:
- Cae por el escenario: El zombie no tiene un Collider o Rigidbody configurado correctamente, o su posición inicial está por debajo de la NavMesh o de cualquier superficie.
- Se teletransporta al origen (0,0,0) o a otro lugar: Esto puede pasar si el NavMeshAgent no encuentra una NavMesh válida para moverse o si hay un problema con su Base Offset.
- El modelo es tan pequeño que parece desaparecer: Menos probable, pero a veces pasa si la escala es incorrecta.
Soluciones y Verificaciones Paso a Paso:
Verificación 1: Posición Inicial del Zombie (Personaje)
- Problema: Si el zombie empieza ligeramente por debajo de la NavMesh o de un objeto con un Collider, puede caer al vacío.
- Solución:
- En la vista “Scene” (Escena), selecciona tu zombie.
- Mira su posición en el “Inspector” (Transform > Position).
- Asegúrate de que su coordenada Y (altura) esté por encima de la superficie de la NavMesh. debe ser solo lo suficiente para que no se “incruste”. Puedes arrastrarlo un poco hacia arriba con la herramienta de movimiento (icono de flechas) para levantarlo visualmente.
Verificación 2: Componentes Físicos (Collider y Rigidbody)
Aunque el NavMeshAgent se encarga del movimiento, los colliders y rigidbodies son importantes para la interacción física y para que el agente se “apoye” en el suelo.
- Problema: Un NavMeshAgent necesita un Collider en el mismo GameObject para funcionar correctamente. A veces, si se usa un Rigidbody sin las configuraciones adecuadas, puede interferir.
Solución:
1: Selecciona tu zombie en la “Hierarchy”.
2: En el “Inspector”, verifica si tiene un componente Collider (por ejemplo, Capsule Collider o Box Collider).
- Si es un modelo 3D con animaciones, un Capsule Collider suele ser lo mejor para personajes.
- Si no tiene ninguno, haz clic en “Add Component”, busca “Capsule Collider” (o “Box Collider” si el diseño del zombie es simple).
- Ajusta el tamaño y la posición del collider para que cubra a tu zombie. El Capsule Collider tiene Center (centro), Radius (radio) y Height (altura).
3: Rigidbody (Cuerpo Rígido):
- Si tu zombie tiene un Rigidbody, asegúrate de que la opción “Is Kinematic” esté marcada. Esto es crucial cuando se usa NavMeshAgent. El NavMeshAgent se encarga del movimiento, y si el Rigidbody no es cinemático, intentará moverlo también, lo que causa conflictos y caídas.
- Si no tiene un Rigidbody y solo tiene un Collider, el NavMeshAgent puede funcionar sin Rigidbody para la navegación básica. Sin embargo, para colisiones con otros objetos móviles o balas, un Rigidbody (cinemático) es necesario.
Ve a la barra de menú superior de Unity: Window > AI > Navigation. Esto abrirá la ventana “Navigation”.
En la ventana “Navigation”, haz clic en la pestaña Agents.
Deberías ver una lista de “Agent Types”. Selecciona el que dice Humanoid (es el predeterminado y el que usaremos).
A la derecha de la ventana “Navigation”, verás la configuración de ese tipo de agente. Ajusta estos valores:
- Radius: Este es el radio del personaje. Para tu zombie, un valor de 0.5 metros es un buen punto de partida (si tu zombie es más grande o más pequeño, ajústalo).
- Height: La altura del personaje. Para tu zombie, un valor de 1.8 a 2.0 metros es bueno.
- Step Height: ¡Este es clave para las cuestas! Es la altura máxima de un “escalón” que el agente puede subir. Para terrenos irregulares como “Flooded Ground”, un valor de 0.3 a 0.4 metros suele funcionar bien. Esto permite que el zombie suba pequeñas elevaciones sin problema.
- Max Slope: El ángulo máximo de inclinación que el agente puede caminar. Un valor de 45 grados es estándar y bueno para la mayoría de las pendientes.
- Drop Height: La altura máxima que el agente puede caer sin considerar un borde como obstáculo. Un valor de 0.5 a 1.0 es seguro.
Configurar el GameManager en el Inspector:
- Selecciona tu GameObject GameManager en la Hierarchy.
- En el Inspector, en el componente Wave Spawner, verás muchos campos nuevos. Necesitas arrastrar y configurar:
- Zombie Prefabs (Array):
- Cambia el Size a 3.
- Arrastra tus tres Prefabs (Zombie1 (1), Zombie_Fast, FatZombie) desde la ventana Project a las ranuras Element 0, Element 1, Element 2.
- Zombie Count Text: Arrastra tu texto de UI (ZombieCountText) desde el GameUICanvas en la Hierarchy.
- Wave Message Text: Arrastra tu texto de UI (WaveMessageText) desde el GameUICanvas en la Hierarchy.
- Ajusta los valores de las nuevas variables a tu gusto para equilibrar la dificultad:
- Base Time Between Waves, Min Time Between Waves, Time Between Waves Decrease Per Wave
- Base Zombie Count, Zombies Increase Per Wave
- Base Spawn Delay, Min Spawn Delay, Spawn Delay Decrease Per Wave
- Base Zombie Health, Health Increase Per Wave
- Base Zombie Damage, Damage Increase Per Wave
- Spawn Radius, Min Distance From Player, Max Height Difference
- Message Display Time, Message Move Time
- Zombie Prefabs (Array):
Sistema de Disparo del Jugador (PlayerShooting.cs)
Este script irá adjunto a tu jugador o a tu cámara principal.
A. Crea el Script:
- En la ventana Project, ve a tu carpeta Scripts.
- Haz clic derecho > Create > cripting > empty C# Script y nómbralo PlayerShooting.
- Abre el script y pega el siguiente código:
using UnityEngine;
public class PlayerShooting : MonoBehaviour
{
public float damagePerShot = 20f; // Cuánto daño hace cada disparo
public- float fireRate = 0.5f; // Tiempo entre disparos (0.5 significa 2 disparos por segundo)
public float weaponRange = 100f; // Alcance del arma (raycast)
public LayerMask shootableLayer; // Define qué capas pueden ser impactadas por el disparo (¡Configurar en Inspector!)
private Camera playerCamera;
private float nextFireTime; // Para controlar el fire rate
void Start()
{
playerCamera = GetComponentInChildren<Camera>(); // Busca la cámara como hijo (si la tienes como hijo del player)
if (playerCamera == null)
{
Debug.LogError(“PlayerShooting: No se encontró una cámara en el jugador o sus hijos. Asegúrate de que el script está en el objeto correcto o que la cámara es hija.”);
enabled = false; // Desactiva el script si no hay cámara
}
}
void Update()
{
// Disparar con el click izquierdo del ratón y si ha pasado suficiente tiempo desde el último disparo
if (Input.GetMouseButtonDown(0) && Time.time >= nextFireTime)
{
Shoot();
nextFireTime = Time.time + fireRate; // Establecer el tiempo del próximo disparo
}
}
void Shoot()
{
RaycastHit hit; // Almacena la información de lo que golpeó el raycast
Ray ray = playerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); // Rayo desde el centro de la pantalla
// Realiza el raycast
if (Physics.Raycast(ray, out hit, weaponRange, shootableLayer))
{
Debug.Log(“Golpeó: ” + hit.collider.name);
// Intentar obtener el componente ZombieAI del objeto golpeado
ZombieAI zombie = hit.collider.GetComponent<ZombieAI>();
if (zombie != null)
{
zombie.TakeDamage(damagePerShot); // Llama al método TakeDamage del zombie
}
// Opcional: Si tienes efectos de disparo o impacto, actívalos aquí
// Instantiate(hitEffectPrefab, hit.point, Quaternion.LookRotation(hit.normal));
}
else
{
Debug.Log(“No golpeó nada o solo golpeó capas no disparables.”);
}
}
B. Configuración en Unity:
- Adjunta PlayerShooting.cs: Arrastra este script a tu GameObject Player (o el que contenga tu cámara principal).
- Configura la LayerMask:
- En el Inspector del PlayerShooting script, verás un campo Shootable Layer.
- Haz clic en el menú desplegable y selecciona Add Layer….
- Crea una nueva Layer (Capa) llamada “Enemy” (o “Zombie”).
- Vuelve al PlayerShooting script en el Inspector y ahora selecciona la capa “Enemy” en Shootable Layer.
- Asigna la Capa “Enemy” a tus Prefabs de Zombies:
- Selecciona cada uno de tus Prefabs de zombies (Zombie1 (1), Zombie_Fast, FatZombie) en la ventana Project.
- En el Inspector de cada Prefab, arriba de su nombre, verás un menú desplegable Layer.
- Selecciona la capa “Enemy” que acabas de crear. ¡Esto es crucial para que el raycast los detecte!
- Ajusta el Damage Per Shot, Fire Rate y Weapon Range en el Inspector del PlayerShooting según tu gusto.
2. Indicador de Vida para Zombies (ZombieAI.cs y UI)
Cada zombie tendrá su propia barra de vida flotante.
A. Modificaciones en el Prefab de Zombie (UI):
- Abre uno de tus Prefabs de Zombie en modo de edición (haz doble clic en el Prefab en la ventana Project)
- Crea un Canvas para la barra de vida:
- En la Hierarchy del Prefab, haz clic derecho > UI > Canvas.
- Selecciona el nuevo Canvas. En su Inspector, cambia Render Mode a World Space.
- Ajusta el Rect Transform del Canvas:
- Pos Y: Un valor positivo para que esté sobre la cabeza del zombie (ej. 2).
- Width: 100 (o 1000, depende de tu escala, tendrás que ajustar).
- Height: 10 (o 100).
- Scale: Ajusta X, Y, Z a un valor muy pequeño (ej. 0.01 o 0.005) para que la barra de vida no sea gigante en el mundo.
- Quita el componente Canvas Scaler del Canvas (haz clic en los tres puntos y Remove Component).
- Crea la Barra de Vida (UI Image):
- Dentro del Canvas que acabas de crear (en la Hierarchy del Prefab), haz clic derecho > UI > Image. Este será el fondo de la barra de vida.
- Nómbralo HealthBarBackground.
- Cambia su Color a un gris oscuro o negro.
- Ajusta su Rect Transform para que ocupe todo el Canvas: Left: 0, Top: 0, Right: 0, Bottom: 0.
- Dentro de HealthBarBackground, haz clic derecho > UI > Image. Este será el relleno de la barra de vida.
- Nómbralo HealthBarFill.
- Cambia su Color a verde.
- En su Inspector, en Image (Script), cambia Image Type a Filled.
- Cambia Fill Method a Horizontal.
- Fill Origin a Left.
- Ajusta su Rect Transform para que ocupe el mismo espacio que el fondo.
- Haz que la Barra de Vida Mire a la Cámara (Nuevo Script Billboard.cs):
- Crea un nuevo script llamado Billboard.cs.
- Pega el siguiente código
using UnityEngine;
public class Billboard : MonoBehaviour
{
public Camera mainCamera; // Asigna_cámara_principal
void Start()
{
if (mainCamera == null)
{
mainCamera = Camera.main;
}
if (mainCamera == null)
{
Debug.LogError(“Billboard: No_se_encontró_la cámara_principal. Asegúrate_de_que_tu_cámara_tenga_el_Tag_’MainCamera’.”);
enabled = false;
}
}
void LateUpdate() // LateUpdate asegura ejecutar después de movimiento de cámara
{
if (mainCamera != null)
{
// Objeto mira cámara, solo en el eje Y (para que no se incline)
transform.LookAt(transform.position + mainCamera.transform.rotation * Vector3.forward,
mainCamera.transform.rotation * Vector3.up);
}
}
Configuración Final en los Prefabs de Zombies:
- Selecciona cada uno de tus Prefabs de Zombies en la ventana Project.
- En el Inspector, en el componente Zombie AI (Script), arrastra el HealthBarFill (la imagen verde que creaste dentro del Canvas del zombie) a la ranura Health Bar Fill Image.
- En el Canvas de cada Prefab de Zombie, en el script Billboard.cs, arrastra tu Main Camera (el GameObject de tu cámara principal en la escena) a la ranura Main Camera. Asegúrate de que tu cámara principal tenga el tag MainCamera.
3. Sistema de Vida del Jugador (PlayerHealth.cs y UI)
A. Crea el Script PlayerHealth.cs:Crea un nuevo script llamado PlayerHealth.
using_UnityEngine;
using UnityEngine.UI; // Necesario para Slider
using TMPro; // Necesario para TextMeshPro (si lo usas para mostrar vida numérica)
using_UnityEngine.SceneManagement; // Necesario para reiniciar la escena
public class PlayerHealth : MonoBehaviour
{
public float maxHealth = 100f;
/public float currentHealth;
public Slider healthBarSlider; // Referencia al Slider de la UI para la barra de vida
// public TextMeshProUGUI healthText; // Opcional: para mostrar la vida como número
public GameObject gameOverPanel; // Referencia al Panel de Game Over
/public TextMeshProUGUI gameOverMessageText; // Mensaje de Game Over
public Button restartButton; // Botón de reinicio
private WaveSpawner waveSpawner; // Para notificar que el jugador murió y detener oleadas
void Awake() // Usar Awake para asegurar que se inicialice antes que otros scripts dependientes
{
currentHealth = maxHealth;
if (healthBarSlider != null)
{
healthBarSlider.maxValue = maxHealth;
healthBarSlider.value = currentHealth;
}
// if (healthText != null) healthText.text = currentHealth.ToString();
if (gameOverPanel != null)
{
gameOverPanel.SetActive(false); // Asegurarse de que el panel de Game Over esté oculto al inicio
}
// Encontrar el WaveSpawner para detener las oleadas al morir el jugador
waveSpawner = FindObjectOfType<WaveSpawner>();
if (waveSpawner == null)
{
Debug.LogWarning(“PlayerHealth: No se encontró WaveSpawner. Asegúrate de que esté en la escena si necesitas detener las oleadas al morir.”);
}
// Asignar la función al botón de reinicio
if (restartButton != null)
{
restartButton.onClick.AddListener(RestartGame);
}
}
public void TakeDamage(float amount)
{
currentHealth -= amount;
currentHealth = Mathf.Max(currentHealth, 0); // Asegurarse de que la salud no baje de 0
if (healthBarSlider != null)
{
healthBarSlider.value = currentHealth;
}
// if (healthText != null) healthText.text = currentHealth.ToString();
Debug.Log($”Jugador recibió {amount} de daño. Salud restante: {currentHealth}”);
if (currentHealth <= 0)
{
Die();
}
}
public void Heal(float amount)
{
currentHealth += amount;
currentHealth = Mathf.Min(currentHealth, maxHealth); // Asegurarse de que la salud no supere la máxima
if (healthBarSlider != null)
{
healthBarSlider.value = currentHealth;
}
// if (healthText != null) healthText.text = currentHealth.ToString();
Debug.Log($”Jugador curado {amount}. Salud actual: {currentHealth}”);
}
void Die()
{
Debug.Log(“¡El jugador ha muerto!”);
// Aquí puedes detener el tiempo, deshabilitar el movimiento del jugador, etc.
Time.timeScale = 0f; // Detener el tiempo del juego
if (gameOverPanel != null)
{
gameOverPanel.SetActive(true);
if (gameOverMessageText != null)
{
gameOverMessageText.text = “¡Has Perdido!”;
}
}
// Opcional: Deshabilitar el script de movimiento y disparo del jugador
PlayerShooting playerShooting = GetComponent<PlayerShooting>();
if (playerShooting != null) playerShooting.enabled = false;
// Si tienes un script de movimiento de jugador, desactívalo también.
// Notificar al WaveSpawner que el juego ha terminado
if (waveSpawner != null)
{
// Puedes añadir un método en WaveSpawner para manejar el fin de juego,
// por ahora, simplemente detendrá el flujo de oleadas al no haber más jugador.
}
}
public void RestartGame()
{
Time.timeScale = 1f; // Reanudar el tiempo del juego antes de reiniciar la escena
SceneManager.LoadScene(SceneManager.GetActiveScene().name); // Recarga la escena actual
}
}
Paso a Paso: Implementando el Disparo (M1911 de Nokobot)
Asunción importante: Estás utilizando el sistema de Input System (New) de Unity, ya que lo configuramos antes.
Fase 1: Configuración del Arma en la Jerarquía del Jugador
Primero, necesitamos que el arma esté visible y correctamente posicionada en la mano de tu personaje.
- Localiza tu Prefab de Personaje:
- En la ventana Hierarchy, selecciona tu GameObject de personaje principal (probablemente Player o ThirdPersonController).
- Encuentra el Hueso de la Mano Correcto:
- Expande la jerarquía de tu personaje en el Hierarchy hasta encontrar el hueso de la mano derecha (o izquierda, según prefieras que dispare). Esto suele estar en una ruta similar a:
- Player > PlayerArmature > Root > Hips > Spine > Spine1 > Spine2 > RightShoulder > RightArm > RightForeArm > RightHand.
- Selecciona el hueso RightHand (o LeftHand).
- Expande la jerarquía de tu personaje en el Hierarchy hasta encontrar el hueso de la mano derecha (o izquierda, según prefieras que dispare). Esto suele estar en una ruta similar a:
- Posiciona el Arma en la Mano:
- En la ventana Project, busca tu prefab o modelo de la M1911 del asset de Nokobot.
- Arrastra el prefab/modelo de la M1911 desde la ventana Project y suéltalo sobre el hueso RightHand que seleccionaste en la Hierarchy. Esto hará que la M1911 se convierta en un hijo de RightHand.
- Con la M1911 seleccionada en la Hierarchy, usa las herramientas de Move (W), Rotate (E) y Scale (R) para posicionar la M1911 en la mano de tu personaje. Ajusta su posición (Position), rotación (Rotation) y tamaño (Scale) hasta que se vea natural en la mano.
- Sugerencia: Puedes entrar en modo Play brevemente para ver cómo se ve con la animación de inactividad, y luego ajustar los valores en el Inspector. Los cambios en modo Play no se guardan, así que anota los valores y luego aplícalos fuera del modo Play.
- Crea un Punto de Disparo (FirePoint):
- Dentro de la jerarquía de tu M1911, haz clic derecho en el GameObject M1911 y selecciona Create Empty. Llama a este nuevo GameObject FirePoint.
- Mueve y posiciona este FirePoint exactamente en la punta del cañón de la M1911. Este será el punto desde donde saldrán tus rayos de disparo o balas. Es importante que esté correctamente posicionado.
Fase 2: Script de Disparo (WeaponController.cs)
Ahora, vamos a crear la lógica de disparo.
- Crea un Nuevo Script:
- En tu carpeta Assets/Scripts (o donde guardes tus scripts), haz clic derecho Create > C# Script.
- Llama al script WeaponController.
- Adjunta el Script a la M1911:
- Arrastra el script WeaponController que acabas de crear desde la ventana Project y suéltalo sobre el GameObject M1911 en tu Hierarchy.
Abre el Script WeaponController.cs y Pega el Código:
using UnityEngine;
using System.Collections; // Necesario para Coroutines
// Asegúrate de que este namespace coincida con el de tus Starter Assets si los tienes
namespace StarterAssets
{
public class WeaponController : MonoBehaviour
{
[Header(“Weapon Settings”)]
/public float Damage = 10f; // Daño que hace el arma
public float Range = 100f; // Alcance máximo del disparo
public float FireRate = 0.1f; // Cadencia de disparo (segundos entre disparos)
[Header(“References”)]
/public Camera PlayerCamera; // La cámara principal de tu jugador (generalmente CinemachineTarget o Main Camera)
public Transform FirePoint; // El punto desde donde saldrá el rayo/bala (el GameObject vacío que creaste)
/public GameObject MuzzleFlashEffect; // El GameObject del efecto de flash de la boca del cañón
public Animator WeaponAnimator; // El Animator del arma (M1911)
private StarterAssetsInputs _input; // Referencia a tu script de entrada
private float _nextTimeToFire = 0f; // Controla la cadencia de disparo
void Start()
{
// Busca el componente StarterAssetsInputs en los padres o en el mismo objeto del personaje.
// Asume que StarterAssetsInputs está en el GameObject raíz del personaje.
_input = GetComponentInParent<StarterAssetsInputs>();
if (_input == null)
{
Debug.LogError(“WeaponController: StarterAssetsInputs script not found on parent!”);
}
if (PlayerCamera == null)
{
// Intentar encontrar la cámara principal si no está asignada en el Inspector
PlayerCamera = Camera.main;
if (PlayerCamera == null)
{
Debug.LogError(“WeaponController: PlayerCamera not assigned and MainCamera not found!”);
}
}
if (FirePoint == null)
{
Debug.LogError(“WeaponController: FirePoint not assigned! Create an empty GameObject at the gun’s muzzle.”);
}
if (MuzzleFlashEffect != null)
{
MuzzleFlashEffect.SetActive(false); // Asegúrate de que el flash esté desactivado al inicio
}
}
void Update()
{
// Si el jugador presiona el botón de disparo y ha pasado suficiente tiempo desde el último disparo
if (_input.shoot && Time.time >= _nextTimeToFire)
{
_nextTimeToFire = Time.time + FireRate; // Calcula cuándo se podrá disparar de nuevo
Shoot(); // Llama a la función de disparo
}
}
void Shoot()
{
// — 1. Animación de Disparo —
if (WeaponAnimator != null)
{
// Asegúrate de tener un Trigger en tu Animator llamado “Fire” (o el que uses para disparar)
WeaponAnimator.SetTrigger(“Fire”);
}
// — 2. Efecto de Muzzle Flash —
if (MuzzleFlashEffect != null)
{
StartCoroutine(MuzzleFlashRoutine()); // Inicia una Coroutine para activar y desactivar el flash
}
// — 3. Detección de Impacto (Raycasting) —
RaycastHit hit;
/*Dispara un rayo desde la cámara o desde el FirePoint si quieres que sea más preciso
// Para 3ra persona, a menudo se usa la cámara para simular la “mira” del jugador
// Si quieres que el rayo salga del arma en su dirección, usa FirePoint.forward
/* Opción 1: Raycast desde el centro de la pantalla (más común para ThirdPerson)
// Esto simula que el jugador apunta con la mira
Ray ray = PlayerCamera.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out hit, Range))
{
Debug.Log(“Hit: ” + hit.collider.name);
// Aquí puedes añadir lógica para:
/* – Instanciar un efecto de impacto (ej. un agujero de bala) en hit.point
/* – Llamar a un método de daño en el objeto impactado (ej. hit.collider.GetComponent<Health>().TakeDamage(Damage);)
}
// Opción 2: Raycast desde el FirePoint del arma (más preciso para el arma, pero menos para la mira del jugador)
/* Esto podría no coincidir con el centro de la pantalla si no tienes un sistema de mira muy preciso
if (Physics.Raycast(FirePoint.position, FirePoint.forward, out hit, Range))
{
Debug.Log(“Hit (from gun): ” + hit.collider.name);
}
Para visualizar el rayo en el editor */
Debug.DrawRay(ray.origin, ray.direction * Range, Color.red, 1f);
}
// Coroutine para el efecto de flash de la boca del cañón
IEnumerator MuzzleFlashRoutine()
{
MuzzleFlashEffect.SetActive(true);
yield return new WaitForSeconds(0.05f); // Duración del flash (ajusta este valor)
MuzzleFlashEffect.SetActive(false);
}
}
4: Configuración del Action Map (Input System)
Ahora que el script está listo, necesitamos configurar el Input System para reconocer el botón de disparo.
- Abre tu PlayerInput Action Map:
- En la ventana Project, busca tu asset de Input Actions. Suele llamarse StarterAssets.inputactions o PlayerInputActions.
- Haz doble clic en él para abrir la ventana Input Actions
- Añade una Nueva Acción de Disparo:
- En la columna de Action Maps, selecciona Player.
- En la columna de Actions, haz clic en el botón + (el signo de más).
- Nombra la nueva acción Shoot.
- Asigna un Botón a la Acción Shoot:
- Con la acción Shoot seleccionada, en la columna Properties (la más a la derecha), haz clic en el botón + debajo de No Bindings.
- Haz clic en Listen. Ahora, presiona el botón que quieres usar para disparar (por ejemplo, el botón izquierdo del mouse).
- Unity debería detectar Mouse/leftButton. Si es correcto, haz clic en Listen de nuevo para dejar de escuchar.
- Guarda el Asset de Input Actions:
- Haz clic en el botón Save Asset en la parte superior derecha de la ventana Input Actions.
Fase 5: Conectar Elementos en el Inspector
Finalmente, conecta todas las referencias en el Inspector de tu M1911.
- Selecciona tu GameObject M1911 en la Hierarchy.
- En su Inspector, busca el componente WeaponController.
- Arrastra y asigna los siguientes campos:
- Player Camera: Arrastra tu Main Camera (o el Cinemachine Virtual Camera Target) desde la Hierarchy hasta este campo.
- Fire Point: Arrastra el GameObject FirePoint que creaste dentro de la M1911 hasta este campo.
- Muzzle Flash Effect: Expande la jerarquía de tu M1911 para encontrar el GameObject que contiene el efecto de flash (podría llamarse “MuzzleFlash”, “Flash”, etc.). Arrástralo a este campo.
- Weapon Animator: Arrastra el GameObject M1911 (o el hijo que tenga el Animator si no es el padre) hasta este campo.
Fase 6: Configuración del Animator del Arma
- Selecciona tu GameObject M1911 en la Hierarchy.
- Abre la ventana Animator (Window > Animation > Animator).
- Crea un Trigger para el disparo:
- En la ventana Animator, ve a la pestaña Parameters (normalmente a la izquierda).
- Haz clic en el botón + y selecciona Trigger.
- Nómbralo Fire (debe coincidir exactamente con el nombre WeaponAnimator.SetTrigger(“Fire”); en el script).
- Configura la Transición a la Animación de Disparo:
- Arrastra tu clip de animación de disparo (el que se llama Fire o similar) desde la ventana Project a la ventana Animator para crear un nuevo estado.
- Haz clic derecho en tu estado Any State (o tu estado de animación base, como Idle) y selecciona Make Transition hacia tu nueva animación de disparo.
- Haz clic en la flecha de transición. En el Inspector, en la sección Conditions, haz clic en + y selecciona tu Trigger Fire.
- Asegúrate de que Has Exit Time esté desactivado en la transición para que el disparo se reproduzca instantáneamente.
- Opcionalmente, puedes crear una transición de la animación Fire de vuelta al estado Idle cuando la animación de Fire termine (con Has Exit Time activado en esa transición).
Parte 1: Crear la Mira (Crosshair) en la UI
Primero, vamos a poner la imagen de la mira en el centro de tu pantalla.
- Crea un Canvas para la UI:
- En la ventana Hierarchy, haz clic derecho.
- Selecciona UI > Canvas.
- Renombra el nuevo GameObject a UICrosshairCanvas.
- Configura el Canvas:
- Selecciona UICrosshairCanvas en la Hierarchy.
- En el Inspector, en el componente Canvas:
- Cambia Render Mode a Screen Space – Camera.
- Arrastra tu Main Camera (o la cámara a la que está siguiendo Cinemachine, si es diferente) al campo Render Camera.
- En el componente Canvas Scaler:
- Cambia UI Scale Mode a Scale With Screen Size.
- Pon Reference Resolution a 1920 x 1080 (o la resolución base de tu juego).
- Pon Screen Match Mode a Match Width Or Height.
- Pon Match a 0.5 (para equilibrar ancho y alto).
- Añade la Imagen de la Mira (Crosshair Image):
- Haz clic derecho en UICrosshairCanvas en la Hierarchy.
- Selecciona UI > Image.
- Renombra el nuevo GameObject a CrosshairImage.
- Configura la Imagen de la Mira:
- Selecciona CrosshairImage en la Hierarchy.
- En el Inspector, en el componente Rect Transform:
- Haz clic en el cuadro de anclas (el que parece un cuadrado con dos flechas) y selecciona el ancla del centro (la que parece una cruz). Mantén Alt presionado al hacer clic para también establecer la posición.
- Asegúrate de que Pos X, Pos Y, Left, Right, Top, Bottom estén todos en 0.
- Ajusta Width y Height a un tamaño pequeño, por ejemplo, 20 o 30.
- En el componente Image:
- En el campo Source Image, arrastra la textura de tu mira. Si no tienes una, puedes usar un sprite blanco temporalmente (crea uno con Assets > Create > Sprites > Square, luego cambia su color en el Inspector).
- Asegúrate de que Color sea blanco (RGBA 255,255,255,255) por ahora.
Parte 2: Preparar a los Zombies (Capa y Script de Salud)
Necesitamos que los zombies sean reconocibles para nuestro raycast y que puedan recibir daño.
- Crea una Capa (Layer) para los Zombies:
- Selecciona uno de tus GameObjects de zombie en la Hierarchy.
- En el Inspector, en la parte superior, al lado de Layer, haz clic en el menú desplegable.
- Selecciona Add Layer….
- En el primer espacio vacío (por ejemplo, User Layer 8 o User Layer 9), escribe Zombie.
- Vuelve a seleccionar tu GameObject de zombie en la Hierarchy.
- Cambia su Layer al recién creado Zombie.
- IMPORTANTE: Si te pregunta Change Children? (¿Cambiar hijos?), selecciona Yes, change children para que todas las partes del modelo de tu zombie estén en la misma capa.
- Repite esto para todos los GameObjects raíz de tus zombies en la escena.
- Crea un Script de Salud para los Zombies (Health.cs):
- En tu carpeta Scripts (o donde organices tus scripts), haz clic derecho.
- Selecciona Create > C# Script.
- Nómbralo Health.
- Abre el script Health.cs y pega el siguiente código:
Conclusiones
Las herramientas utilizadas demuestran la potencia y versatilidad del ecosistema actual de desarrollo de videojuegos 3D. Mixamo se consolida como una plataforma fundamental para desarrolladores, ofreciendo una amplia biblioteca de personajes y animaciones de alta calidad que acelera significativamente el proceso de prototipado y desarrollo. Su capacidad de proporcionar modelos listos para usar elimina las barreras técnicas iniciales y permite a los desarrolladores concentrarse en la mecánica del juego.
Blender, por su parte, confirma su posición como la herramienta de modelado 3D más completa y accesible del mercado. Su integración con Facebuilder y las capacidades de edición de mallas permiten un nivel de personalización que seria costoso y complejo de lograr desde cero. La naturaleza open-source de Blender lo convierte en una opción especialmente atractiva para desarrolladores independientes y estudios con presupuestos limitados.
Unity demuestra ser mucho más que un motor de juegos; es una plataforma integral que proporciona herramientas sofisticadas como el sistema NavMesh, el Animator Controller y el sistema IK. Estas características permiten implementar comportamientos complejos de IA y sistemas de animación avanzados sin requerir programación desde cero. La documentación extensa y la comunidad activa de Unity facilitan la resolución de problemas técnicos complejos.
La sinergia entre estas tres herramientas crea un pipeline de desarrollo robusto y eficiente. Mixamo proporciona los assets base, Blender permite la personalización y optimización, y Unity ofrece la plataforma para dar vida a estos elementos con lógica de juego sofisticada. Este enfoque demuestra que las herramientas modernas han democratizado el desarrollo de videojuegos, haciendo accesible la creación de experiencias 3D de calidad profesional para desarrolladores de todos los niveles.
Referencias
Autodesk Inc. (2023). Mixamo (Versión 1.0) [Plataforma de animación 3D]. https://www.mixamo.com/
Blender Foundation. (2023). Blender (Versión 4.0) [Software de modelado 3D]. https://www.blender.org/
Unity Technologies. (2023). Unity (Versión 2023.3 LTS) [Motor de videojuegos]. https://unity.com/
Unity Technologies. (2023). Unity NavMesh and Pathfinding. En Unity User Manual. https://docs.unity3d.com/Manual/nav-NavigationSystem.html
Unity Technologies. (2023). Unity Animation and Animators. En Unity User Manual.
https://docs.unity3d.com/Manual/AnimationOverview.html
Maty Dev. (2024, abril 18). Movimiento y animaciones en Unity 3D - Episodio 2 [Video]. YouTube. https://www.youtube.com/watch?v=FC4IkQsEPfo
Maty Dev. (2024, abril 25). Exportar TU JUEGO En Unity Para ANDROID Correctamente [Video]. YouTube. https://www.youtube.com/watch?v=foTtNekN9LM
Unity Technologies. (2024, septiembre 11). Starter Assets – ThirdPerson | Updates in new CharacterController package (versión 1.1.6) [Recurso de software]. Unity Asset Store. https://assetstore.unity.com/packages/essentials/starter-assets-thirdperson-updates-in-new-charactercontroller-pa-196526
Autores: Diego Gaitán Camargo, Juan Camilo Velasco Vega
Editor: Carlos Iván Pinzón Romero
Código: UCMV-08
Universidad: Universidad Central