Intelligenze dei nemici
Fa parte della sezione Architettura dei giochi in Unity del percorso Unity di Coddy. Lezione 31 di 50.
Il cervello di un nemico è una macchina a stati con uno stato per ogni comportamento. Questo scheletro cammina tra i suoi posti di guardia, insegue un giocatore che si avvicina entro 6 unità e sferra un colpo di spada al secondo quando è vicino. Ogni stato decide da sé come uscirne, e lo scheletro mantiene gli helper che condividono:
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; }
}
}Gli stati vengono creati una sola volta e restano in vita quanto lo scheletro, quindi i loro campi persistono tra una visita e l'altra. next di PatrolState mantiene il suo valore anche dopo un inseguimento, e lo scheletro continua a camminare verso il posto di guardia che stava raggiungendo. Enter reimposta solo ciò che deve ricominciare da capo: AttackState imposta cooldown a 0, così il primo colpo arriva appena lo scheletro raggiunge il giocatore.
Tutti i movimenti passano attraverso WalkTo, qui MoveTowards sul transform, che fa camminare attraverso i muri. Per muovere lo scheletro con un Rigidbody, invece, WalkTo imposta la velocità del corpo e lo scheletro aggiorna la macchina in FixedUpdate, dove Time.deltaTime è il passo della fisica. I tre stati non cambiano: indicano dove andare, il proprietario indica come.
Sfida
MedioUn cavaliere fa la guardia al cortile del castello. Le frecce guidano il Player, che ha Health con TakeDamage(int amount) (entrambi già pronti). Knight.cs contiene i campi che l’Inspector compila (waypoints da W0 a W2, player, playerHealth, speed 3 e gli intervalli indicati sotto) e i metodi di supporto che usano il suo Rigidbody: DistanceTo(point) e PlayerDistance() misurano la distanza sul terreno, WalkTo(target) cammina verso un punto, Stop() resta fermo. Dagli un cervello:
- In
Knight.cs, crea la macchina e i tre stati (campipatrol,chaseeattack), avvia il pattugliamento e aggiorna la macchina in FixedUpdate. - In
KnightStates.cs, scriviPatrolState,ChaseStateeAttackState, ognuno dei quali riceve il cavaliere nel costruttore. - Pattugliamento: cammina verso
waypoints[next]; quando è a meno di 0.3 da esso, dirigiti verso il successivo e, dopo l’ultimo, torna al primo. Se il giocatore è più vicino disightRange(5), inseguilo. Dopo un inseguimento, la pattuglia riprende verso il waypoint che stava raggiungendo. - Inseguimento: cammina verso il giocatore. Se è più vicino di
attackRange(1.5), attacca. Se è più lontano diloseRange(8), torna a pattugliare. Tra 5 e 8 l’inseguimento continua, così il cavaliere non passa continuamente da uno stato all’altro al limite del suo campo visivo. - Attacco: resta fermo e colpisci il giocatore subito per 1 danno, poi ogni secondo, finché si trova entro
attackRange; quando è più lontano, torna a inseguirlo.
Premi Play, lascia che il cavaliere ti veda, subisci un colpo, poi scappa e infine premi Check.
Provalo tu
using UnityEngine;
// PatrolState, ChaseState e AttackState
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Architettura dei giochi in Unity
1Componenti, non gerarchie
Componenti piccoliUno script base per i nemiciComponenti astrattiComponenti che collaboranoRiepilogo: torretta modulare4Dati serializzati
Classi serializzabiliListe nell'InspectorEnum nell'InspectorRiepilogo: tabella del bottino7Macchine a stati
Gli stati come classiUna macchina a statiTransizioniIntelligenze dei nemiciRiepilogo: stati della porta10Progetto: Sopravvissuto nell’arena
L’eroeTipi di nemici5ScriptableObjects
Asset di datiStatistiche delle armiImpostazioni condiviseOndate come datiRiepilogo: database degli oggetti