Comportement des PNJ : machines à états, arbres de comportement, GOAP et PNJ pilotés par IA
Créez votre propre comportement de PNJ
Comment les PNJ décident-ils de ce qu'ils doivent faire ? Machines à états, arbres de comportement, GOAP, IA utilitaire et PNJ pilotés par LLM — expliqués avec des exemples 3D en direct dans le navigateur.
Le comportement des PNJ est la logique qui transforme un modèle, une barre de vie et une capsule de collision en quelque chose qui semble vouloir des choses. Il répond à une question en boucle, généralement à chaque image ou à un tick fixe : compte tenu de ce que je sais du monde, que dois-je faire ensuite ? Tout le reste sur cette page — machines à états finis, arbres de comportement, GOAP, IA utilitaire, intention pilotée par LLM — est une réponse différente à cette question, avec des coûts et des modes d'échec différents.
Nous construisons des PNJ qui s'exécutent dans un onglet de navigateur, au sein du créateur de jeux 3D de Neta Studio. Cette contrainte façonne les conseils ci-dessous. Il n'y a pas de serveur dédié, pas de thread physique séparé, et pas de budget pour un planificateur qui cherche pendant 40 millisecondes à chaque tick. Si une technique ne peut pas être rendue assez légère pour s'exécuter à côté du rendu sur un ordinateur portable de milieu de gamme, nous la dégradons ou nous la sortons du chemin critique. Les sections qui suivent décrivent ce que nous livrons réellement, et où chaque approche se casse.
Réponse rapide : comment un PNJ décide
Un PNJ décide en exécutant une fonction de décision selon un calendrier, en lisant une petite tranche de l'état du monde (distance à la cible, ligne de vue, santé actuelle, dernière position connue), et en renvoyant une action que les systèmes de déplacement et d'animation peuvent exécuter. Le calendrier compte autant que la logique. Des décisions sur un tick de 100 ms à 250 ms, avec déplacement et collision à chaque image, coûtent une fraction de ce que coûte une logique de décision par image et sont indiscernables pour un joueur dans la plupart des cas.
Les quatre modèles classiques diffèrent par l'endroit où réside la complexité. Une machine à états encode les décisions sous forme de transitions entre des états nommés. Un arbre de comportement les encode sous forme de hiérarchie de nœuds sélecteurs et séquences. GOAP les encode sous forme d'un objectif plus un ensemble d'actions avec préconditions et effets, et recherche un plan. L'IA utilitaire les encode sous forme d'une fonction de score sur chaque action candidate. Les PNJ pilotés par LLM encodent l'intention de haut niveau en langage naturel et renvoient le résultat à l'un des modèles moins coûteux.
En pratique, on les combine. Un évaluateur utilitaire choisit l'objectif, un arbre de comportement ou une machine à états l'exécute, un navmesh déplace le corps, et le modèle de langage ne touche qu'au dialogue et à l'intention. Le reste de cette page aborde chaque couche à tour de rôle et nomme le mode d'échec précis que vous rencontrerez.
Machines à états (FSM)
Une machine à états finis est le modèle utile le moins coûteux et le bon premier choix pour la plupart des PNJ. Vous définissez un ensemble d'états — Inactif, Patrouille, Poursuite, Attaque, Fuite, Étourdi — et un ensemble de conditions de transition entre eux. À chaque tick, vous évaluez les transitions sortant de l'état courant et, si l'une se déclenche, vous changez. Le coût est proportionnel au nombre de transitions vérifiées, qui est faible. L'état est une seule variable, ce qui le rend trivial à sérialiser, déboguer et afficher dans une superposition d'éditeur.
Le mode d'échec est l'oscillation. Un PNJ à 5,1 mètres du joueur passe de Patrouille à Poursuite, se déplace à 4,9 mètres, et une transition symétrique le renvoie en Patrouille. Il vibre à la frontière pour toujours. La solution est l'hystérésis : entrer en Poursuite à 5,0 mètres et en sortir à 6,5 mètres. Le même problème apparaît avec des vérifications de ligne de vue qui clignotent quand le joueur se déplace latéralement derrière un pilier, et la même solution s'applique — exiger que la condition tienne pendant N ticks consécutifs, ou ajouter une minuterie de mémoire qui maintient le PNJ en Poursuite pendant deux secondes après la dernière observation.
Le deuxième mode d'échec est l'enchevêtrement des transitions. Cinq états nécessitent dix transitions ; dix états en nécessitent près de quarante-cinq. Chaque nouvel état doit être relié à chaque état depuis lequel il peut être atteint, et les conditions se chevauchent de manière difficile à raisonner. Quand votre table de transitions cesse de tenir sur un seul écran, c'est le signal pour passer à un arbre de comportement.
Une FSM concrète pour une garde en patrouille : Inactif (attendre 2 s) → Patrouille (marcher vers le prochain point de passage) → Alerte (se tourner vers le bruit, 1,5 s) → Poursuite (se déplacer vers la dernière position connue en courant) → Attaque (si à moins de 2 m et temps de recharge écoulé) → Recherche (errer près de la dernière position connue pendant 8 s) → Patrouille. Sept états, une douzaine de transitions environ, et c'est la minuterie de mémoire dans Recherche qui rend le comportement délibéré plutôt que fébrile.
- État à variable uniqueL'état courant est une seule énumération. Déboguer, c'est lire une valeur, pas reconstruire l'intention à partir d'une pile.
- Hystérésis sur chaque seuilToute distance ou minuterie pouvant transiter dans les deux sens a besoin de valeurs d'entrée et de sortie différentes, sinon le PNJ oscille à la frontière.
- Budget de transitionsGardez les états sous une dizaine environ avant que la table ne devienne ingérable. Au-delà, passez à un arbre de comportement ou scindez en sous-machines.
Arbres de comportement
Un arbre de comportement remplace la table de transitions par une hiérarchie. Les nœuds feuilles sont des actions (MoveTo, PlayAnimation, Wait) et des conditions (IsPlayerVisible, HealthBelow). Deux types de nœuds composites font l'essentiel du travail. Un sélecteur essaie ses enfants dans l'ordre et renvoie un succès dès que l'un réussit — c'est votre liste de priorités. Une séquence exécute ses enfants dans l'ordre et échoue dès que l'un échoue — c'est votre procédure étape par étape.
Le gain d'échelle est la composabilité. Ajouter un nouveau comportement consiste à ajouter un sous-arbre sous un sélecteur, et non à relier un nouvel état à chaque état depuis lequel il peut être atteint. Les priorités sont explicites dans l'ordre des enfants, ce qui est bien plus facile à lire qu'un ensemble de conditions booléennes qui se chevauchent. Les sous-arbres peuvent être réutilisés : la même branche « enquêter sur un bruit » fonctionne pour une garde, un chien et un civil.
Les arbres ne sont pas automatiquement plus rapides. Une implémentation naïve réévalue tout l'arbre chaque fois qu'il est tické, c'est pourquoi vous continuez à ticker selon le calendrier de décision de 100 à 250 ms et laissez l'action en cours piloter le déplacement à chaque image. Les actions longues ont besoin d'un état en cours que l'arbre mémorise, sinon une action redémarre de zéro à chaque tick et le PNJ revient au début de son chemin. La plupart des moteurs qui livrent un arbre de comportement — le paquet Behavior d'Unity, l'arbre intégré d'Unreal, ou un petit arbre personnalisé en TypeScript — gèrent cela avec une valeur de retour « running », mais c'est la première chose à vérifier quand un PNJ saccade.
Une structure de sélecteur pour un PNJ de combat, par ordre de priorité : (1) si la santé est inférieure à 25 pour cent, séquence [Fuir vers un abri, Se soigner] ; (2) si le joueur est visible et à moins de 2 m, séquence [Se tourner vers le joueur, Attaquer] ; (3) si le joueur est visible, séquence [Se déplacer vers la position d'esquive, Attaquer] ; (4) si la dernière position connue est définie, Rechercher ; (5) Patrouiller. Le sélecteur choisit la première branche dont la condition passe. Réordonner ces enfants, c'est la façon de régler l'agressivité sans toucher au code.
- Nœud sélecteurRenvoie un succès au premier enfant qui réussit. Utilisez-le pour les priorités de repli : essayez le comportement le plus spécifique, puis le suivant, puis celui par défaut.
- Nœud séquenceÉchoue au premier enfant qui échoue. Utilisez-le pour les procédures ordonnées où l'étape deux n'a pas de sens si l'étape un ne s'est pas produite.
- État en coursLes actions longues doivent renvoyer « running » et reprendre là où elles se sont arrêtées. Si elles renvoient un succès immédiatement, l'arbre les redémarre à chaque tick et le déplacement se casse.
- Tableau noirUn magasin clé-valeur partagé pour les faits que l'arbre lit et écrit — position de la cible, niveau d'alerte, expiration du temps de recharge. Gardez-le petit et typé.
GOAP (planification d'actions orientée objectifs)
GOAP inverse le modèle. Au lieu de décrire quand transiter, vous décrivez ce que font les actions et ce qu'elles exigent, et un planificateur recherche une séquence qui atteint un objectif. Chaque action a des préconditions, des effets et un coût. « Attack » exige HasWeapon et InRange, et définit TargetDead. « GetWeapon » exige WeaponAvailable et définit HasWeapon. « MoveToTarget » exige TargetKnown et définit InRange. Le planificateur part de l'objectif, travaille à rebours, et trouve la chaîne d'actions la moins coûteuse dont les effets le satisfont.
Cela paie lorsque l'état du monde est riche et que la séquence correcte n'est pas évidente. Un PNJ qui doit rompre la ligne de vue, recharger, se repositionner puis engager produit un plan que le concepteur n'a jamais écrit explicitement, parce que le planificateur a découvert l'ordre à partir du graphe de préconditions. Cela paie aussi en production, car ajouter un nouvel objet ou une nouvelle capacité revient à ajouter une définition d'action, et non à modifier chaque machine à états qui devrait en tenir compte.
Le coût, c'est la recherche. Un planificateur naïf explore les combinaisons d'actions jusqu'à trouver un plan, ce qui peut exploser à mesure que vous ajoutez des actions et des variables d'état. L'atténuation standard est une heuristique — A* sur le graphe d'états avec une estimation de distance vers l'objectif — plus une limite d'expansion stricte. Dans nos propres contextes de navigateur, les plans sont calculés sur le tick de décision avec un plafond de quelques centaines d'expansions de nœuds, et tout ce qui le dépasse retombe sur un plan mis en cache ou une simple branche d'arbre de comportement. La limite honnête est la suivante : GOAP vaut son coût de recherche quand le nombre de séquences d'actions pertinentes est grand, et c'est une surcharge gaspillée quand le PNJ ne fait jamais que trois choses.
- Préconditions et effetsChaque action déclare ce qui doit être vrai pour s'exécuter et ce qu'elle change. Le planificateur raisonne sur ces déclarations, pas sur le code.
- Recherche à rebours depuis l'objectifPartez de l'état souhaité et trouvez les actions dont les effets le produisent, puis récursez sur leurs préconditions. Généralement moins coûteux que la recherche vers l'avant.
- Limite d'expansionPlafonnez les expansions de nœuds par appel de planification. Dépassez le plafond et retombez — ne laissez jamais un planificateur bloquer une image.
- Mise en cache des plansNe replanifiez que lorsque l'état du monde change de façon significative. Les situations statiques réutilisent le plan précédent à coût nul.
IA utilitaire
L'IA utilitaire note chaque option et choisit la plus élevée. Chaque action candidate a une fonction qui associe l'état du monde à un nombre, généralement normalisé de zéro à un. « Fuir » obtient un score élevé quand la santé est basse et que des ennemis sont proches. « Attaquer » obtient un score élevé quand la cible est à portée et que les munitions sont supérieures à zéro. « Errer » obtient un score modéré quand rien d'autre ne presse. Le PNJ prend l'argmax, éventuellement avec une petite variation aléatoire pour que les égalités se résolvent de façon imprévisible.
C'est le modèle qui produit les personnages les plus surprenants en apparence, parce que le comportement émerge de l'interaction des courbes plutôt que de règles explicites. Deux PNJ avec des pondérations de score légèrement différentes se comportent sensiblement différemment sans aucun code de branchement, ce qui en fait le choix naturel pour la variation de personnalité. C'est aussi le modèle le plus facile à régler à l'exécution : exposez les pondérations dans un panneau de débogage et faites-les glisser pendant que le jeu tourne.
Le mode d'échec est une utilité plate ou bruitée. Si chaque action obtient 0,4, le PNJ semble indécis et change constamment. Si le score le plus élevé bascule à chaque tick entre deux valeurs proches, vous obtenez la même oscillation qu'avec une mauvaise machine à états. La solution standard est une fenêtre d'engagement : une fois une action choisie, engagez-la au minimum pour une durée fixe — 500 ms est une valeur par défaut raisonnable — sauf si le score le plus élevé dépasse celui de l'action engagée d'une marge significative. Les courbes comptent aussi. Des courbes de réponse linéaires sur la santé produisent un PNJ qui fuit exactement au même moment à chaque combat. Des courbes exponentielles ou logistiques produisent des seuils qui évoluent avec la situation.
- Fonctions de score par actionChaque action associe l'état du monde à une utilité de zéro à un. Gardez-les indépendantes pour pouvoir en régler une sans toucher aux autres.
- Fenêtre d'engagementUne fois choisie, maintenez une action pendant une durée minimale. Empêche l'alternance à chaque tick entre des scores presque égaux.
- Pondérations réglablesExposez les pondérations en direct. Deux PNJ ne différant que par leurs pondérations se lisent comme des personnages différents, ce qui est le système de personnalité le moins coûteux qui existe.
- Courbes de réponseLes courbes linéaires créent des seuils fixes. Les courbes logistiques font réagir le PNJ à la situation plutôt qu'à un nombre magique.
Navigation et collisions
La logique de décision choisit une destination. La navigation y amène le corps. Un navmesh est une surface praticable triangulée ; la recherche de chemin exécute A* sur son graphe de polygones, et le résultat est une liste d'arêtes de polygones que le PNJ suit. Nous construisons le nôtre au chargement à partir de la géométrie du niveau et le mettons en cache, car le reconstruire à chaque image est la façon la plus simple de faire chuter le taux de rafraîchissement dans un jeu de navigateur.
Le steering gère les derniers mètres et le déplacement instantané. Seek et arrive produisent le vecteur avant, la séparation éloigne le PNJ des agents proches, et l'évitement d'obstacles courbe le chemin autour des bloqueurs dynamiques comme une porte ou un autre personnage. Exécutez le steering à chaque image ; n'exécutez la recherche de chemin que lorsque la destination change ou que le chemin est invalidé. Un chemin recalculé à chaque image pour chaque PNJ est un coût courant et évitable.
La collision est la passe qui empêche un PNJ de traverser un mur. Le navmesh garde le chemin sur un sol praticable, mais le steering et la physique peuvent encore pousser un personnage dans la géométrie, aussi le contrôleur de personnage résout-il la pénétration contre les colliders après le déplacement. Dans nos mondes isométriques — Mini World et The Cyclops' Island — c'est de là que viennent la plupart des bugs visibles des PNJ : un chemin qui se faufile dans un interstice plus étroit que le rayon du personnage, ou un vecteur de steering qui pousse un agent dans un mur à un coin. Régler correctement le rayon de l'agent sur le navmesh, et maintenir la force de séparation sous la force de poussée du contrôleur, résout la majorité du problème.
01
Faites cuire le navmesh au chargement à partir de la géométrie statique et mettez-le en cache. Ne le reconstruisez pas à chaque image.
02
Réglez le rayon et la hauteur de l'agent pour correspondre à la capsule de collision réelle du personnage, pas à la boîte englobante du modèle.
03
N'exécutez la recherche de chemin que lorsque la destination change, que le chemin est bloqué, ou que le PNJ s'est écarté du chemin au-delà d'un seuil.
04
Exécutez le steering, la séparation et l'évitement d'obstacles à chaque image avec une force de séparation plafonnée.
05
Résolvez la collision contre les colliders après le déplacement, puis ramenez le personnage sur le navmesh.
06
Journalisez et visualisez le chemin et le navmesh dans une superposition de débogage — les bugs sont généralement spatiaux, pas logiques.
PNJ pilotés par LLM
Un modèle de langage change la donne au niveau de l'intention et du dialogue, pas de la navigation. La recherche de chemin, le steering et la collision restent exactement ce qu'ils étaient, car un modèle qui met des centaines de millisecondes à répondre ne peut pas piloter un corps qui doit bouger à chaque image. Ce que le modèle peut faire, c'est décider quel objectif a du sens étant donné les mots d'un joueur, générer un texte de réponse dans le personnage, et choisir entre des options de haut niveau quand la situation est ambiguë et que les règles écrites à la main produiraient quelque chose de plat.
Une architecture viable sépare les deux vitesses. Les réactions immédiates tournent sur la machine à états ou l'évaluateur utilitaire — attaquer, fuir, se mettre à couvert — parce qu'elles doivent se produire en une ou deux images. L'intention délibérée passe par le modèle à une cadence bien plus lente, peut-être une fois par interaction ou toutes les quelques secondes, et sa sortie est une commande structurée que la couche rapide exécute : définir l'objectif sur Suivre, définir l'attitude envers le joueur sur Hostile, définir la réplique de dialogue. Le modèle ne touche jamais directement à la position.
Les limites honnêtes sont la latence, le coût et le déterminisme. Les temps de réponse sont assez longs pour que le PNJ doive accuser réception de l'entrée immédiatement par un geste ou une courte réplique enregistrée, sinon il paraît cassé. Le coût évolue avec la fréquence des appels, donc vous regroupez et mettez en cache. Et comme la sortie est non déterministe, vous devez la valider contre un schéma avant qu'elle n'atteigne le jeu — une réponse en texte libre qui ne se parse pas doit retomber sur une réplique par défaut, pas faire planter le système de dialogue. Traitez le modèle comme un service non fiable derrière une interface typée, et l'intégration tient.
- Sortie de commande typéeLe modèle renvoie une intention structurée, pas de la prose que le code du jeu parse. Validez contre un schéma et rejetez tout ce qui est malformé.
- Boucle à deux vitessesLogique locale rapide pour les réactions, appels lents au modèle pour la délibération. Ne mettez jamais un aller-retour réseau sur le chemin du déplacement.
- Accusé de réception immédiatJouez un geste ou une courte réplique enregistrée dès que l'entrée arrive, puis remplacez-la quand le modèle répond. Masque la latence.
- Repli gracieuxSi l'appel échoue ou expire, retombez sur la réponse par défaut de la machine à états. Le PNJ ne doit jamais rester bloqué à attendre un modèle.
Entrez dans le monde auquel il appartient
Un sprite est un personnage sans endroit où se tenir. La dernière étape est celle que personne d'autre n'offre : prenez la planche de sprites que vous venez de créer et placez-la dans un monde où l'on peut se promener — une petite île, une planète, un coin de rue pluvieux. Chaque monde sur ce site a été créé à partir d'une seule description écrite et publié comme une page jouable.
Retour au créateur de jeux 3D
Les 31 mondes, jeux et scènes 3D terminés vivent sur une seule page — tous jouables.