Programación de Movimiento Aleatorio para Enemigos en Juegos de Rol

Clase 21 de 60Curso Avanzado de Creación de RPGs con Unity

Resumen

La mejor forma de dar vida a un enemigo en Unity 2D es con una IA simple de movimiento aleatorio. Aquí verás cómo estructurar un EnemyController en C#, conectar Rigidbody2D y Animator, y controlar tiempos de paso con precisión. Todo está preparado para reutilizarlo en distintos tipos de enemigos sin cambiar el script, solo los gráficos.

¿Cómo crear e integrar el enemy controller?

Para empezar, se crea un script de C# llamado EnemyController y se arrastra al objeto enemigo en la jerarquía. Aunque se llame controller, no lo controlará el jugador: gobierna el movimiento autónomo del enemigo.

¿Qué requisitos debe tener el enemigo?

  • Tag "enemy" aplicada al objeto enemigo.
  • Sistema de animaciones con blend tree y parámetros "horizontal" y "vertical".
  • Collider configurado.
  • Rigidbody2D agregado.

¿Cómo resolver errores del editor?

  • Si Visual Studio marca todo en rojo sin motivo: guardar, cerrar y reabrir el script desde Unity.

¿Qué variables controlan la IA y el movimiento aleatorio?

La lógica se basa en dos temporizadores: tiempo entre pasos y tiempo de cada paso. Además, se registra si el enemigo está en movimiento y en qué dirección debe desplazarse. Se comunican los ejes al animator mediante constantes.

using UnityEngine; public class EnemyController : MonoBehaviour { public float enemySpeed = 1f; // Velocidad del enemigo. private Rigidbody2D enemyRigidBody; // Referencia al Rigidbody2D. private bool isMoving; // Estado de movimiento. public float timeBetweenSteps = 2f; // Tiempo en reposo entre pasos. private float timeBetweenStepsCounter; // Contador interno de reposo. public float timeToMakeStep = 1.5f; // Duración de cada paso. private float timeToMakeStepCounter; // Contador interno de paso. public Vector2 directionToMakeStep; // Dirección del movimiento. private Animator enemyAnimator; // Referencia al Animator. private const string Horizontal = "horizontal"; // Parámetro del blend tree. private const string Vertical = "vertical"; // Parámetro del blend tree. void Start() { enemyRigidBody = GetComponent<Rigidbody2D>(); enemyAnimator = GetComponent<Animator>(); // Inicialización de contadores con los valores públicos. timeBetweenStepsCounter = timeBetweenSteps; timeToMakeStepCounter = timeToMakeStep; }
  • enemySpeed: controla la rapidez del desplazamiento.
  • isMoving: indica si se está moviendo o está quieto.
  • timeBetweenSteps/timeBetweenStepsCounter: reposo y su contador interno.
  • timeToMakeStep/timeToMakeStepCounter: duración del paso y su contador interno.
  • directionToMakeStep: vector de dirección aleatoria.
  • enemyRigidBody y enemyAnimator: componentes para movimiento y animación.
  • "horizontal" y "vertical": nombres exactos de los parámetros del animator.

¿Cómo se programa la lógica en update para moverse y parar?

La mecánica alterna entre reposo y movimiento. En movimiento, se consume el contador del paso y se aplica velocidad en la dirección elegida. En reposo, se espera hasta agotar el contador y se decide una nueva dirección aleatoria.

void Update() { if (isMoving) { // Mientras se mueve. timeToMakeStepCounter -= Time.deltaTime; enemyRigidBody.velocity = directionToMakeStep * enemySpeed; if (timeToMakeStepCounter < 0f) { // Fin del paso: parar y preparar reposo. isMoving = false; timeBetweenStepsCounter = timeBetweenSteps; // Ojo con asignar el correcto. enemyRigidBody.velocity = Vector2.zero; } } else { // Mientras está quieto. timeBetweenStepsCounter -= Time.deltaTime; if (timeBetweenStepsCounter < 0f) { // Toca iniciar un nuevo paso. isMoving = true; timeToMakeStepCounter = timeToMakeStep; // Dirección aleatoria en X e Y entre -1 y 1. directionToMakeStep = new Vector2( Random.Range(-1f, 1f), Random.Range(-1f, 1f) ); } } // Notificar la dirección al Animator para el blend tree. enemyAnimator.SetFloat(Horizontal, directionToMakeStep.x); enemyAnimator.SetFloat(Vertical, directionToMakeStep.y); } }

¿Qué ocurre mientras se mueve?

  • Se decrementa el contador del paso con Time.deltaTime.
  • Se aplica velocity al rigidbody según la dirección y la velocidad.
  • Al agotar el tiempo del paso: se detiene, se pone Vector2.zero y se arma el reposo.

¿Qué ocurre mientras está quieto?

  • Se decrementa el contador de reposo con Time.deltaTime.
  • Al agotarse: se activa el movimiento, se reinicia el contador del paso y se elige una dirección aleatoria con Random.Range(-1, 1) en X e Y (puede salir diagonal).

¿Cómo sincronizar animaciones con el movimiento?

  • Se actualizan los parámetros "horizontal" y "vertical" mediante SetFloat.

  • El blend tree interpreta estos valores para girar la animación según la dirección.

  • Valores de ejemplo comprobados: enemySpeed = 1. Tiempo entre pasos = 2 s. Tiempo de paso = 1.5 s.

  • Consejo práctico: hay mucho código dentro de Update; avanza con cuidado para no mezclar contadores.

  • Próximo paso: crea un prefab con varios enemigos en pantalla y valida el comportamiento.

¿Tienes dudas o ideas para ampliar esta IA? Comparte tu pregunta y cuéntame cómo la adaptarías a tus enemigos.