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.