Machines à états
Dès qu'un PNJ doit accomplir plus d'une tâche, les scripts amateurs se transforment en un champ de mines de drapeaux booléens : estEnColere, aVuLeJoueur, estEnTrainDeRentrer, rechargeArme. Très vite, ces drapeaux se contredisent, et le personnage bégaye entre marcher, viser et s'enfuir.
Le remède absolu est la machine à états finis (FSM). La règle d'or est simple : une entité se trouve dans un seul et unique état à un instant T.
Les quatre états d'une rencontre
Dans le Projet 05, tu construiras une confrontation de gang articulée autour de quatre états clairs :
[ Calme ] <------------------------+
| |
| (le joueur s'approche) | (le joueur bat en retraite)
v |
[ Alerte ] |
| |
| (arme dégainée ou trop près) |
v |
[ Combat ] -----------------> [ Retour ]
- Calme : Les passants montent la garde ou discutent avec un scénario d'attente.
- Alerte : Le joueur s'approche. Les PNJ se tournent vers lui avec une attitude méfiante.
- Combat : Armes dégainées, tirs nourris et mise à couvert contre le joueur.
- Retour : Le joueur s'enfuit. Les PNJ cessent le feu, baissent leurs armes et reprennent leur ronde.
Modéliser les états en C#
On représente les états par une énumération (enum) :
public enum EtatRencontre
{
Calme,
Alerte,
Combat,
Retour
}
La méthode unique de transition
Ne change jamais d'état en écrasant une variable à vingt endroits disparates de ton script. Passe par une méthode de transition unique pour garantir l'exécution des actions d'entrée et de sortie :
private EtatRencontre _etat = EtatRencontre.Calme;
private void ChangerEtat(EtatRencontre nouvelEtat, Ped acteur, Ped player)
{
if (_etat == nouvelEtat)
{
return;
}
// Actions lors de la SORTIE de l'ancien état
switch (_etat)
{
case EtatRencontre.Combat:
acteur.Task.ClearAll(); // Cesser immédiatement le feu
break;
}
_etat = nouvelEtat;
// Actions lors de l'ENTRÉE dans le nouvel état
switch (_etat)
{
case EtatRencontre.Calme:
acteur.Task.StartScenarioInPlace("WORLD_HUMAN_GUARD_STAND", 0, false);
break;
case EtatRencontre.Alerte:
acteur.Task.LookAt(player, 5000);
GTA.UI.Notification.PostTicker("Le garde vous surveille d'un mauvais œil.", false);
break;
case EtatRencontre.Combat:
acteur.Task.Combat(player, TaskCombatFlags.None, TaskThreatResponseFlags.None);
break;
case EtatRencontre.Retour:
acteur.Task.ClearAll();
acteur.Task.StartScenarioInPlace("WORLD_HUMAN_GUARD_STAND", 0, false);
_etat = EtatRencontre.Calme; // Retour accompli
break;
}
}
La force de l'architecture par états
Remarque la clarté obtenue :
- Un PNJ ne peut jamais être à la fois en
Calmeet enCombat. Les comportements contradictoires deviennent impossibles par conception. - Quand le combat cesse,
Task.ClearAll()est garanti d'être exécuté lors de la sortie de l'étatCombat. - Ajouter un cinquième état (comme
Fuitequand la vie passe sous 20%) demande juste une entrée dans l'enum et deux cas dans le switch.
Dans la leçon suivante, nous étudions la séparation de la logique de jeu et des appels au moteur, préalable indispensable pour tester tes règles sans lancer GTA.