Dans le processus de programmation des entités dans le jeu, des situations surviennent lorsqu'elles doivent agir dans différentes conditions de différentes manières, ce qui suggère l'utilisation d' 
états .
Mais si vous décidez d'utiliser la force brute, le code se transformera rapidement en chaos emmêlé avec de nombreuses instructions imbriquées if-else.
Pour une solution élégante à ce problème, vous pouvez utiliser le modèle de conception État. Nous lui dédierons ce tutoriel!
À partir du didacticiel, vous:
- Apprenez les bases du modèle State dans Unity.
- Vous apprendrez ce qu'est une machine d'état et quand l'utiliser.
- Apprenez à utiliser ces concepts pour contrôler les mouvements de votre personnage.
Remarque : ce tutoriel est destiné aux utilisateurs avancés; il est supposé que vous savez déjà comment travailler dans Unity et que vous avez un niveau moyen de connaissance de C #. De plus, ce didacticiel utilise Unity 2019.2 et C # 7.
Se rendre au travail
Téléchargez le 
matériel du projet . Décompressez le 
fichier zip et ouvrez le projet de 
démarrage dans Unity.
Il existe plusieurs dossiers dans le projet qui vous aideront à démarrer. Le dossier 
Assets / RW contient les dossiers 
Animations , 
Matériaux , 
Modèles , 
Préfabriques , 
Ressources , 
Scènes , 
Scripts et 
Sons , nommés en fonction des ressources qu'ils contiennent.
Pour terminer le didacticiel, nous ne travaillerons qu'avec des 
scènes et des 
scripts .
Allez dans 
RW / Scenes et ouvrez 
Main . En mode Jeu, vous verrez un personnage dans une cagoule à l'intérieur d'un château médiéval.
Cliquez sur 
Play et notez comment la 
caméra se déplace pour s'adapter au cadre du 
personnage . Pour le moment, dans notre petit jeu il n'y a pas d'interactions, nous allons y travailler dans le tutoriel.
Explorez le personnage
Dans la 
hiérarchie, sélectionnez 
Caractère . Découvrez l' 
inspecteur . Vous verrez un 
composant du même nom contenant la logique de contrôle des 
caractères .
Ouvrez 
Character.cs situé dans 
RW / Scripts .
Le script effectue de nombreuses actions, mais la plupart d'entre elles ne sont pas importantes pour nous. Pour l'instant, prêtons attention aux méthodes suivantes.
- Move: il déplace le personnage, recevant des valeurs de type- speedflottante comme vitesse de déplacement et vitesse de- rotationSpeedcomme vitesse angulaire.
- ResetMoveParams: cette méthode réinitialise les paramètres utilisés pour animer le mouvement et la vitesse angulaire du personnage. Il est utilisé uniquement pour le nettoyage.
- SetAnimationBool: il définit le paramètre d'animation param de type Bool sur valeur.
- CheckCollisionOverlap: il reçoit un- Vector3et renvoie un- boolqui détermine s'il y a des collisionneurs dans le rayon spécifié à partir du- TriggerAnimation:- TriggerAnimationle paramètre d'animation de- paramentrée.
- ApplyImpulse:- ApplyImpulseimpulsion au caractère égale à la- forceparamètre d'entrée- forcetype- Vector3.
Ci-dessous, vous verrez ces méthodes. Dans notre tutoriel, leur contenu et leur travail interne ne sont pas importants.
Que sont les machines à états
Une machine d'état est un concept dans lequel un conteneur stocke l'état de quelque chose à un moment donné. Sur la base des données d'entrée, il peut fournir une conclusion en fonction de l'état actuel, en passant dans ce processus à un nouvel état. Les machines à états peuvent être représentées sous forme de 
diagramme d'état . La préparation d'un diagramme d'état vous permet de réfléchir à tous les états possibles du système et aux transitions entre eux.
Machines d'état
Les machines à états finis ou 
FSM (machines à états finis) sont l'une des quatre principales familles de 
machines . Les automates sont des modèles abstraits de machines simples. Ils sont étudiés dans le cadre de la 
théorie des automates - la branche théorique de l'informatique.
En bref:
- FSM se compose d'une quantité limitée de condition . À un moment donné , un seul de ces états est actif .
- Chaque état détermine dans quel état il entrera en sortie sur la base de la séquence reçue d' informations entrantes .
- L'état de sortie devient le nouvel état actif. En d'autres termes, il y a une transition entre les États .
Pour mieux comprendre cela, considérez le caractère d'un jeu de plateforme qui est sur le terrain. Le personnage est à l'état 
permanent . Ce sera son 
état actif jusqu'à ce que le joueur appuie sur le bouton pour que le personnage saute.
L'état 
permanent identifie une pression sur un bouton comme une 
entrée importante et, en tant que 
sortie , passe à l'état de 
saut .
Supposons qu'il existe un certain nombre de tels états de mouvement et qu'un personnage ne puisse être que dans l'un des états à la fois. Ceci est un exemple de FSM.
Machines à états hiérarchiques
Considérons un jeu de plateforme utilisant FSM, dans lequel plusieurs états partagent une logique physique commune. Par exemple, vous pouvez vous déplacer et sauter dans les états 
accroupi et 
debout . Dans ce cas, plusieurs variables entrantes conduisent au même comportement et à la même sortie d'informations pour deux états différents.
Dans une telle situation, il serait logique de déléguer le comportement général à un autre état. Heureusement, cela peut être réalisé en utilisant 
des machines à états 
hiérarchiques .
Dans un FSM hiérarchique, certains 
sous - 
états délèguent des informations entrantes 
brutes à leurs 
sous-états . Cela vous permet à son tour de réduire gracieusement la taille et la complexité du FSM, tout en conservant sa logique.
Modèle de statut
Dans leur livre 
Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson et John Vlissidis ( 
The Gang of Four ) ont défini la 
tâche du modèle State comme suit:
«Il doit permettre à l'objet de changer de comportement lorsque son état interne change. Dans ce cas, il semblerait que l'objet ait changé de classe. »
Pour mieux comprendre cela, considérons l'exemple suivant:
- Un script qui reçoit des informations entrantes pour la logique de mouvement est attaché à une entité en jeu.
- Cette classe stocke une variable d' état actuelle qui fait simplement référence à une instance de la classe d' état .
- Les informations entrantes sont déléguées à cet état actuel, qui les traite et crée un comportement défini en lui-même. Il gère également les transitions d'état requises.
Par conséquent, étant donné qu'à différents moments la variable d' 
état actuelle fait référence à différents états, il semblera que la même classe de script se comporte différemment. C'est l'essence même du modèle «Statut».
Dans notre projet, la classe de 
caractères susmentionnée se comportera différemment selon les différents états. Mais nous avons besoin de lui pour se comporter!
Dans le cas général, il y a trois points clés pour chaque classe d'état qui permettent le comportement de l'état dans son ensemble:
- Entrée : c'est le moment où une entité entre dans un état et effectue des actions qui ne doivent être effectuées qu'une seule fois lors de l'entrée dans l'état.
- Sortie : similaire à l'entrée - toutes les opérations de réinitialisation sont effectuées ici, qui doivent être effectuées uniquement avant que l'état ne change.
- Boucle de mise à jour : voici la logique de mise à jour de base qui s'exécute dans chaque trame. Il peut être divisé en plusieurs parties, par exemple, un cycle de mise à jour de la physique et un cycle de traitement des entrées des joueurs.
Définition d'un état et d'une machine à états
Accédez à 
RW / Scripts et ouvrez 
StateMachine.cs .
La State Machine , comme vous pouvez le deviner, fournit une abstraction pour la State Machine. Notez que 
CurrentState correctement situé à l'intérieur de cette classe. Il stockera un lien vers l'état actuel de la machine à états actifs.
Maintenant, pour définir le concept de l' 
état , allons dans 
RW / Scripts et ouvrons le script 
State.cs dans l'EDI.
State est une classe abstraite que nous utiliserons comme 
modèle à partir duquel toutes les 
classes d'états de projet sont dérivées. Une partie du code dans les documents du projet est déjà prête.
DisplayOnUI affiche uniquement le nom de l'état actuel dans l'interface utilisateur à l'écran. Vous n'avez pas besoin de connaître son périphérique interne, comprenez simplement qu'il reçoit un énumérateur du type 
UIManager.Alignment tant que paramètre d'entrée, qui peut être 
Left ou 
Right . L'affichage du nom de l'état dans la partie inférieure gauche ou droite de l'écran en dépend.
De plus, il existe deux variables protégées, 
character et 
stateMachine . La variable de 
character fait référence à une instance de la classe 
Character et 
stateMachine fait référence à une instance 
de la machine à états associée à l'état.
Lors de la création d'une instance d'état, le constructeur lie 
character et 
stateMachine .
Chacune des nombreuses instances de 
Character dans une scène peut avoir son propre ensemble d'états et de machines à états.
Ajoutez maintenant les méthodes suivantes à 
State.cs et enregistrez le fichier:
 public virtual void Enter() { DisplayOnUI(UIManager.Alignment.Left); } public virtual void HandleInput() { } public virtual void LogicUpdate() { } public virtual void PhysicsUpdate() { } public virtual void Exit() { } 
Ces méthodes virtuelles définissent les points d'état clés décrits ci-dessus. Lorsque 
la machine d'état effectue une transition entre les états, nous appelons 
Exit pour l'état précédent et 
Enter nouvel 
état actif .
HandleInput , 
LogicUpdate et 
PhysicsUpdate définissent ensemble 
une boucle de mise à jour . 
HandleInput gère l'entrée du lecteur. 
LogicUpdate traite la logique de base, tandis que 
PhyiscsUpdate traite les calculs de logique et de physique.
Maintenant, ouvrez à nouveau 
StateMachine.cs , ajoutez les méthodes suivantes et enregistrez le fichier:
 public void Initialize(State startingState) { CurrentState = startingState; startingState.Enter(); } public void ChangeState(State newState) { CurrentState.Exit(); CurrentState = newState; newState.Enter(); } 
Initialize configure la machine à états en définissant 
CurrentState sur 
startingState et en appelant 
Enter pour cela. Cela initialise la machine d'état, définissant pour la première fois l'état actif.
ChangeState gère 
les transitions d' 
état . Il appelle 
Exit pour l'ancien 
CurrentState avant de remplacer sa référence par 
newState . À la fin, il appelle 
Enter pour 
newState .
Ainsi, nous définissons l' 
état et 
la machine d'état .
Création d'états de mouvement
Jetez un œil au diagramme d'état suivant, qui montre les différents 
états de mouvement de l' essence 
du joueur dans le jeu. Dans cette section, nous implémentons le modèle «Status» pour le 
mouvement montré dans la figure 
FSM :
Faites attention aux états de mouvement, à savoir la position 
debout , le 
canardage et le 
saut , ainsi que la façon dont les données entrantes provoquent des transitions entre les états. Il s'agit d'un FSM hiérarchique dans lequel 
Grounded est un sous-état pour les 
sous-états Ducking et 
Standing .
Revenez à Unity et accédez à 
RW / Scripts / States . Vous y trouverez plusieurs fichiers C # dont les noms se terminent par 
State .
Chacun de ces fichiers définit une classe, dont chacune est héritée de 
State . Par conséquent, ces classes définissent les états que nous utiliserons dans le projet.
Ouvrez maintenant 
Character.cs à partir du dossier 
RW / Scripts .
Faites défiler au-dessus du fichier 
#region Variables et ajoutez le code suivant:
 public StateMachine movementSM; public StandingState standing; public DuckingState ducking; public JumpingState jumping; 
Ce 
movementSM fait référence à une machine à états qui traite la logique de mouvement pour l'instance de 
Character . Nous avons également ajouté des liens vers trois états que nous mettons en œuvre pour chaque type de mouvement.
Accédez à 
#region MonoBehaviour Callbacks dans le même fichier. Ajoutez les méthodes 
MonoBehaviour suivantes, puis enregistrez
 private void Start() { movementSM = new StateMachine(); standing = new StandingState(this, movementSM); ducking = new DuckingState(this, movementSM); jumping = new JumpingState(this, movementSM); movementSM.Initialize(standing); } private void Update() { movementSM.CurrentState.HandleInput(); movementSM.CurrentState.LogicUpdate(); } private void FixedUpdate() { movementSM.CurrentState.PhysicsUpdate(); } 
- Dans Startcode crée une instance de State Machine et l'affecte àmovementSM, et instancie également divers états de mouvement. Lors de la création de chacun des états de mouvement, nous transmettons des références à l'instance deCharacterà l'aide duthis, ainsi qu'à l'instance demovementSM. Au final, nous appelonsInitializepourmovementSMet passonsStandingcomme état initial.
- Dans la méthode Update, nous appelonsHandleInputetLogicUpdatepour leCurrentStatede la machinemovementSM. De même, dansFixedUpdatenous appelonsPhysicsUpdatepour leCurrentStatede la machinemovementSM. En substance, cela délègue les tâches à un état actif; c'est la signification du modèle «Statut».
Maintenant, nous devons définir le comportement à l'intérieur de chacun des états de mouvement. Préparez-vous, il y aura beaucoup de code!
Entreprise ferme
Revenez à 
RW / Scripts / States dans la fenêtre Project.
Ouvrez 
Grounded.cs et notez que cette classe a un constructeur qui correspond au constructeur 
State . C'est logique car cette classe en hérite. Vous verrez la même chose dans toutes les autres classes d' 
état .
Ajoutez le code suivant:
 public override void Enter() { base.Enter(); horizontalInput = verticalInput = 0.0f; } public override void Exit() { base.Exit(); character.ResetMoveParams(); } public override void HandleInput() { base.HandleInput(); verticalInput = Input.GetAxis("Vertical"); horizontalInput = Input.GetAxis("Horizontal"); } public override void PhysicsUpdate() { base.PhysicsUpdate(); character.Move(verticalInput * speed, horizontalInput * rotationSpeed); } 
Voici ce qui se passe ici:
- Nous redéfinissons l'une des méthodes virtuelles définies dans la classe parente. Pour conserver toutes les fonctionnalités qui peuvent exister dans le parent, nous appelons la méthode de baseavec le même nom de chaque méthode substituée. Il s'agit d'un modèle important que nous continuerons à utiliser.
- La ligne suivante, EnterdéfinithorizontalInputetverticalInputleurs valeurs par défaut.
- Dans Exitnous appelons, comme mentionné ci-dessus, la méthodeResetMoveParams- Dans la méthode HandleInput, les variableshorizontalInputetverticalInputHandleInputvaleurs des axes d'entrée horizontal et vertical. Grâce à cela, le joueur peut contrôler le personnage à l'aide des touches W , A , S et D.
- À PhysicsUpdatenous effectuons un appelMove, en passant les variableshorizontalInputetverticalInputmultipliées par les vitesses correspondantes. Dans laspeedvariablespeedla vitesse de déplacement est stockée, et dansrotationSpeed, la vitesse angulaire.
Maintenant, ouvrez 
Standing.cs et faites attention au fait qu'il hérite de 
Grounded . Cela s'est produit parce que, comme nous l'avons dit plus haut, 
Standing est un sous-état de 
Grounded . Il existe différentes façons de mettre en œuvre cette relation, mais dans ce didacticiel, nous utilisons l'héritage.
Ajoutez les méthodes de 
override suivantes et enregistrez le script:
 public override void Enter() { base.Enter(); speed = character.MovementSpeed; rotationSpeed = character.RotationSpeed; crouch = false; jump = false; } public override void HandleInput() { base.HandleInput(); crouch = Input.GetButtonDown("Fire3"); jump = Input.GetButtonDown("Jump"); } public override void LogicUpdate() { base.LogicUpdate(); if (crouch) { stateMachine.ChangeState(character.ducking); } else if (jump) { stateMachine.ChangeState(character.jumping); } } 
- Dans Enternous configurons les variables héritées deGrounded. Appliquez laspeedrotationSpeedet laspeedrotationSpeedRotationSpeedpersonnage à laspeedet à laspeedrotationSpeed. Ensuite, ils se rapportent, respectivement, à la vitesse normale de mouvement et à la vitesse angulaire destinée à l'essence du personnage.
 
 De plus, les variables de stockage des entréescrouchetcrouchsont réinitialisées à faux.
- À l'intérieur de HandleInput, les variablesHandleInputetjumpstockent les entrées du joueur pour les squats et les sauts. Si dans la scène principale le joueur appuie sur la touche Maj, le squat est réglé sur vrai. De même, un joueur peut utiliser la touche Espace pourjump.
- Dans LogicUpdatenous vérifions les variablesLogicUpdateetjumpde typebool. Sicrouchest vrai, alorsmovementSM.CurrentStatetransforme encharacter.ducking. Sijumpest vrai, l'état change encharacter.jumping.
Enregistrez et assemblez le projet, puis cliquez sur 
Lecture . Vous pouvez vous déplacer dans la scène à l'aide des touches 
W , 
A , 
S et 
D. Si vous essayez d'appuyer sur 
Maj ou 
Espace , un comportement inattendu se produira, car les états correspondants ne sont pas encore implémentés.
Essayez de vous déplacer sous les objets de la table. Vous verrez qu'en raison de la hauteur du collisionneur du personnage, ce n'est pas possible. Pour que le personnage le fasse, vous devez ajouter un comportement accroupi.
On grimpe sous la table
Ouvrez le script 
Ducking.cs . Notez que 
Ducking hérite également de la classe 
Grounded pour les mêmes raisons que 
Standing . Ajoutez les méthodes de 
override suivantes et enregistrez le script:
 public override void Enter() { base.Enter(); character.SetAnimationBool(character.crouchParam, true); speed = character.CrouchSpeed; rotationSpeed = character.CrouchRotationSpeed; character.ColliderSize = character.CrouchColliderHeight; belowCeiling = false; } public override void Exit() { base.Exit(); character.SetAnimationBool(character.crouchParam, false); character.ColliderSize = character.NormalColliderHeight; } public override void HandleInput() { base.HandleInput(); crouchHeld = Input.GetButton("Fire3"); } public override void LogicUpdate() { base.LogicUpdate(); if (!(crouchHeld || belowCeiling)) { stateMachine.ChangeState(character.standing); } } public override void PhysicsUpdate() { base.PhysicsUpdate(); belowCeiling = character.CheckCollisionOverlap(character.transform.position + Vector3.up * character.NormalColliderHeight); } 
- Dans Enterparamètre qui provoque le basculement de l'animation de squat est réglé sur accroupi, ce qui active l'animation de squat. Les propriétéscharacter.CrouchSpeedetcharacter.CrouchRotationSpeedles valeurs despeedet derotation, qui renvoient le mouvement et la vitesse angulaire du personnage lorsqu'il se déplace dans un squat .
 
 character.CrouchColliderHeightsuivant.CrouchColliderHeight définit la taille du collisionneur du personnage, qui renvoie la hauteur de collision souhaitée lors de l'accroupissement. À la fin,belowCeilingréinitialisé sur false.
- Dans Exitle paramètre d'animation squat est défini sur false. Cela désactive l'animation de squat. Ensuite, la hauteur normale du collisionneur est définie, renvoyée parcharacter.NormalColliderHeight.
- À l'intérieur de HandleInputvariablecrouchHelddéfinit la valeur d'entrée du lecteur. Dans la scène principale , maintenir Shift définitcrouchHeldsur true.
- Dans PhysicsUpdatevariablebelowCeilingattribuer une valeur en passant un point au formatVector3avec la tête de l'objet de jeu du personnage à la méthodeCheckCollisionOverlap. S'il y a une collision près de ce point, cela signifie que le personnage est sous une sorte de plafond.
- En interne, LogicUpdatevérifie sicrouchHeldoubelowCeilingest vrai. Si aucune d'entre elles n'est vraie, alorsmovementSM.CurrentStatedevientcharacter.standing.
Générez le projet et cliquez sur 
Play . Vous pouvez maintenant vous déplacer dans la scène. Si vous appuyez sur 
Shift , le personnage s'assoit et vous pouvez vous déplacer dans le squat.
Vous pouvez également monter sous la plate-forme. Si vous relâchez 
Shift sous les plates-formes, le personnage sera toujours dans un squat jusqu'à ce qu'il quitte son abri.
Envolez-vous!
Ouvrez 
Jumping.cs . Vous verrez une méthode appelée 
Jump . Ne vous inquiétez pas de son fonctionnement; il suffit de comprendre qu'il est utilisé pour que le personnage puisse sauter en tenant compte de la physique et de l'animation.
Ajoutez maintenant les méthodes de 
override habituelles et enregistrez le script
 public override void Enter() { base.Enter(); SoundManager.Instance.PlaySound(SoundManager.Instance.jumpSounds); grounded = false; Jump(); } public override void LogicUpdate() { base.LogicUpdate(); if (grounded) { character.TriggerAnimation(landParam); SoundManager.Instance.PlaySound(SoundManager.Instance.landing); stateMachine.ChangeState(character.standing); } } public override void PhysicsUpdate() { base.PhysicsUpdate(); grounded = character.CheckCollisionOverlap(character.transform.position); } 
- À l'intérieur d' EntersingletonSoundManagerjoue le son du saut. Lagroundedréinitialisée à sa valeur par défaut. À la fin,Jumpest appelé.
- Dans PhysicsUpdatepointPhysicsUpdatecôté des jambes du personnage est envoyé àCheckCollisionOverlap, ce qui signifie que lorsque le personnage est au sol, lagroundedsera définie sur true.
- Dans LogicUpdate, si lagroundedest vraie, nous appelonsTriggerAnimationpour activer l'animation d'atterrissage, un son d'atterrissage est joué et lemovementSM.CurrentStatechange encharacter.standing.
Ainsi, sur ce point, nous avons achevé la mise en œuvre complète du déplacement des FSM en utilisant 
le modèle «État» . Générez le projet et exécutez-le. Appuyez sur 
Espace pour faire sauter le personnage.
Où aller ensuite?
Les 
matériaux du projet ont un projet de projet et un projet fini.
Malgré son utilité, les machines à états ont des limites. Les machines à états simultanés et les automates à refoulement peuvent gérer certaines de ces limitations. Vous pouvez les lire dans le livre de Robert Nystrom 
Game Programming Patterns .
De plus, le sujet peut être approfondi en examinant les 
arbres de comportement utilisés pour créer des entités plus complexes dans le jeu.