Cerebros de enemigos
Parte de la sección Arquitectura de juegos en Unity del Journey de Unity de Coddy. Lección 31 de 50.
El cerebro de un enemigo es una máquina de estados con un estado por comportamiento. Este esqueleto camina entre sus puestos, persigue a un jugador que se acerca a menos de 6 unidades y blande su espada una vez por segundo cuando está cerca. Cada estado decide por sí mismo cómo salir, y el esqueleto conserva los ayudantes que comparten:
public class Skeleton : MonoBehaviour
{
public Transform player;
public Transform[] posts;
public float speed = 2f;
public StateMachine machine = new StateMachine();
public IState patrol, chase, attack;
void Start()
{
patrol = new PatrolState(this);
chase = new ChaseState(this);
attack = new AttackState(this);
machine.ChangeState(patrol);
}
void Update() { machine.Tick(); }
public float PlayerDistance() { return Vector3.Distance(transform.position, player.position); }
public void WalkTo(Vector3 target)
{
target.y = transform.position.y;
transform.position = Vector3.MoveTowards(transform.position, target, speed * Time.deltaTime);
}
}
public class PatrolState : IState
{
private Skeleton skel;
private int next;
public PatrolState(Skeleton skel) { this.skel = skel; }
public void Enter() { }
public void Exit() { }
public void Tick()
{
if (skel.PlayerDistance() < 6f) { skel.machine.ChangeState(skel.chase); return; }
Vector3 post = skel.posts[next].position;
post.y = skel.transform.position.y;
skel.WalkTo(post);
if (skel.transform.position == post) next = (next + 1) % skel.posts.Length;
}
}
public class ChaseState : IState
{
private Skeleton skel;
public ChaseState(Skeleton skel) { this.skel = skel; }
public void Enter() { }
public void Exit() { }
public void Tick()
{
float d = skel.PlayerDistance();
if (d > 6f) { skel.machine.ChangeState(skel.patrol); return; }
if (d < 1.5f) { skel.machine.ChangeState(skel.attack); return; }
skel.WalkTo(skel.player.position);
}
}
public class AttackState : IState
{
private Skeleton skel;
private float cooldown;
public AttackState(Skeleton skel) { this.skel = skel; }
public void Enter() { cooldown = 0f; }
public void Exit() { }
public void Tick()
{
if (skel.PlayerDistance() > 1.5f) { skel.machine.ChangeState(skel.chase); return; }
cooldown -= Time.deltaTime;
if (cooldown <= 0f) { Debug.Log("Swing!"); cooldown = 1f; }
}
}Los estados se crean una vez y viven tanto como el esqueleto, así que sus campos perduran entre visitas. El next de PatrolState sigue establecido después de una persecución, y el esqueleto continúa caminando hacia el puesto al que se dirigía. Enter reinicia solo lo que debe empezar de nuevo: el de AttackState establece cooldown en 0, así que el primer golpe llega en cuanto el esqueleto alcanza al jugador.
Todo el movimiento pasa por WalkTo, aquí MoveTowards sobre el transform, que atraviesa las paredes. Para mover al esqueleto con un Rigidbody, en cambio, WalkTo establece la velocidad del cuerpo y el esqueleto actualiza la máquina en FixedUpdate, donde Time.deltaTime es el paso de física. Los tres estados no cambian: indican adónde ir, el propietario indica cómo.
Desafío
IntermedioUn caballero protege el patio del castillo. Las flechas mueven al jugador, cuya Health tiene TakeDamage(int amount) (ambos preparados). Knight.cs tiene los campos que rellena el Inspector (waypoints de W0 a W2, player, playerHealth, speed 3 y los rangos de abajo) y métodos auxiliares que usan su Rigidbody: DistanceTo(point) y PlayerDistance() miden sobre el suelo, WalkTo(target) camina hacia un punto y Stop() se queda quieto. Dale un cerebro:
- En
Knight.cs, crea la máquina y los tres estados (campospatrol,chaseyattack), inicia la patrulla y actualiza la máquina en FixedUpdate. - En
KnightStates.cs, escribePatrolState,ChaseStateyAttackState, cada uno tomando al caballero en su constructor. - Patrulla: camina hasta
waypoints[next]; cuando esté a menos de 0.3, dirígete al siguiente punto y, después del último, vuelve al primero. Si el jugador está a menos desightRange(5), persíguelo. Después de una persecución, la patrulla continúa hacia el punto de ruta al que se dirigía. - Persecución: camina hacia el jugador. Si está a menos de
attackRange(1.5): ataca. Si está a más deloseRange(8): patrulla. Entre 5 y 8, la persecución continúa, para que el caballero no alterne entre las dos acciones en el límite de su campo de visión. - Ataque: quédate quieto y golpea al jugador por 1 de inmediato y, después, cada segundo mientras esté dentro de
attackRange; cuando esté más lejos, vuelve a perseguirlo.
Pulsa Play, deja que el caballero te vea, recibe un golpe, luego huye y, después, pulsa Check.
Pruébalo tú mismo
using UnityEngine;
// PatrolState, ChaseState y AttackState
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Arquitectura de juegos en Unity
1Componentes, no jerarquías
Componentes pequeñosUn script base para enemigosComponentes abstractosComponentes que trabajan juntosRepaso: torreta modular4Datos serializados
Clases serializablesListas en el InspectorEnumeraciones en el InspectorRepaso: tabla de botín7Máquinas de estados
Estados como clasesUna máquina de estadosTransicionesCerebros de enemigosRepaso: estados de puertas10Proyecto: Superviviente de la arena
El héroeTipos de enemigos2Interfaces en Unity
IDamageableInterfaces y TryGetComponentGetComponentsRepaso: tecla de interacción5ScriptableObjects
Recursos de datosEstadísticas de armasConfiguración compartidaOleadas como datosRepaso: base de datos de objetos