Les cerveaux des ennemis
Fait partie de la section Architecture de jeu dans Unity du Journey Unity de Coddy. Leçon 31 sur 50.
Le cerveau d’un ennemi est une machine à états avec un état par comportement. Ce squelette se déplace entre ses postes, poursuit un joueur qui s’approche à moins de 6 unités et donne un coup d’épée une fois par seconde quand il est proche. Chaque état décide de sa propre sortie, et le squelette conserve les assistants qu’ils partagent :
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; }
}
}Les états sont créés une seule fois et restent actifs aussi longtemps que le squelette, leurs champs persistent donc d’une visite à l’autre. Le next de PatrolState conserve sa valeur après une poursuite, et le squelette continue son chemin vers le poste qu’il visait. Enter ne réinitialise que ce qui doit repartir de zéro : celle d’AttackState définit cooldown sur 0, afin que le premier coup soit donné dès que le squelette atteint le joueur.
Tous les déplacements passent par WalkTo, ici MoveTowards sur le transform, qui traverse les murs. Pour déplacer le squelette avec un Rigidbody à la place, WalkTo définit la vélocité du corps et le squelette fait avancer la machine dans FixedUpdate, où Time.deltaTime correspond au pas de physique. Les trois états ne changent pas : ils indiquent où aller, le propriétaire indique comment.
Défi
MoyenUn chevalier garde la cour du château. Les flèches indiquent le chemin du joueur, dont la Health possède TakeDamage(int amount) (les deux sont prêts). Knight.cs contient les champs que l’Inspector remplit (waypoints de W0 à W2, player, playerHealth, speed à 3 et les portées ci-dessous) ainsi que des méthodes d’aide utilisant son Rigidbody : DistanceTo(point) et PlayerDistance() mesurent la distance au sol, WalkTo(target) se dirige vers un point, Stop() reste immobile. Donne-lui un cerveau :
- Dans
Knight.cs, crée la machine et les trois états (champspatrol,chaseetattack), démarre la patrouille et fais avancer la machine à chaque FixedUpdate. - Dans
KnightStates.cs, écrisPatrolState,ChaseStateetAttackState, chacun prenant le chevalier dans son constructeur. - Patrouille : dirige-toi vers
waypoints[next]; à moins de 0.3 de celui-ci, dirige-toi vers le suivant, puis retourne au premier après le dernier. À moins desightRange(5), le joueur est poursuivi. Après une poursuite, la patrouille reprend vers le point de passage qu’elle visait. - Poursuite : dirige-toi vers le joueur. À moins de
attackRange(1.5) : attaque. Au-delà deloseRange(8) : patrouille. Entre 5 et 8, la poursuite continue, afin que le chevalier n’alterne pas entre les deux états à la limite de son champ de vision. - Attaque : reste immobile et frappe immédiatement le joueur pour 1, puis toutes les secondes, tant qu’il est à moins de
attackRange; lorsqu’il est plus loin, reprends la poursuite.
Appuie sur Play, laisse le chevalier te voir, prends un coup, puis cours, et enfin appuie sur Check.
Essayez vous-même
using UnityEngine;
// PatrolState, ChaseState et AttackState
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Architecture de jeu dans Unity
1Des composants, pas des hiérarchies
Des composants simplesUn script de base pour les ennemisDes composants abstraitsDes composants qui fonctionnent ensembleRécapitulatif : tourelle modulaire4Données sérialisées
Classes sérialisablesListes dans l’inspecteurÉnumérations dans l’inspecteurRécapitulatif : table de butin7Machines à états
Les états sous forme de classesUne machine à étatsTransitionsLes cerveaux des ennemisRécapitulatif : états d’une porte10Projet : Survivant de l’arène
Le hérosTypes d’ennemis2Interfaces dans Unity
IDamageableInterfaces et TryGetComponentGetComponentsRécapitulatif : touche d’interaction5ScriptableObjects
Assets de donnéesStatistiques des armesParamètres partagésLes vagues sous forme de donnéesRécapitulatif : base de données d’objets