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?
¿Tienes dudas o ideas para ampliar esta IA? Comparte tu pregunta y cuéntame cómo la adaptarías a tus enemigos.