Vous êtes-vous déjà demandé comment dans des jeux comme
Super Meat Boy la fonction de relecture est implémentée? L'une des façons de le mettre en œuvre consiste à effectuer l'entrée de la même manière que le joueur, ce qui, à son tour, signifie que l'entrée doit être stockée d'une manière ou d'une autre. Vous pouvez utiliser
le modèle de commande pour cela et bien plus encore.
Le modèle de commande est également utile pour créer des fonctions d'annulation et de rétablissement dans un jeu de stratégie.
Dans ce didacticiel, nous implémentons le modèle de commande en C # et l'utilisons pour guider le personnage du bot à travers un labyrinthe en trois dimensions. À partir du didacticiel, vous apprendrez:
- Les bases du modèle de commande.
- Comment implémenter le modèle de commande
- Comment créer une file d'attente de commandes d'entrée et retarder leur exécution.
Remarque : il est supposé que vous connaissez déjà Unity et que vous avez une connaissance moyenne de C #. Dans ce didacticiel, nous travaillerons avec Unity 2019.1 et C # 7 .
Se rendre au travail
Pour commencer, téléchargez le
matériel du
projet . Décompressez le fichier et ouvrez le projet
Starter dans Unity.
Allez dans
RW / Scenes et ouvrez la scène
principale . La scène se compose d'un bot et d'un labyrinthe, ainsi que d'une interface utilisateur terminale qui affiche des instructions. Le level design est réalisé sous la forme d'une grille, ce qui est utile lorsque l'on déplace visuellement le bot à travers le labyrinthe.
Si vous cliquez sur
Play , nous verrons que les instructions ne fonctionnent pas. Ceci est normal car nous ajouterons cette fonctionnalité au didacticiel.
La partie la plus intéressante de la scène est le
Bot GameObject. Sélectionnez-le dans la fenêtre Hiérarchie en cliquant dessus.
Dans l'inspecteur, vous pouvez voir qu'il a un composant
Bot . Nous utiliserons ce composant en émettant des commandes d'entrée.
Nous comprenons la logique du bot
AccĂ©dez Ă
RW / Scripts et ouvrez le script
Bot dans l'éditeur de code. Vous n'avez pas besoin de savoir ce qui se passe dans le script
Bot . Mais jetez un œil à deux méthodes:
Move
et
Shoot
. Encore une fois, vous n'avez pas à comprendre ce qui se passe à l'intérieur de ces méthodes, mais vous devez comprendre comment les utiliser.
Notez que la méthode
Move
reçoit un paramètre d'entrée
CardinalDirection
.
CardinalDirection
est une énumération. Un élément d'énumération de type
CardinalDirection
peut ĂŞtre
Up
,
Down
,
Right
ou
Left
. Selon la
CardinalDirection
sélectionnée
CardinalDirection
bot se déplace exactement d'un carré le long de la grille dans la direction correspondante.
La méthode
Shoot
oblige le bot à tirer des obus qui détruisent les
murs jaunes , mais sont inutiles contre les autres murs.
Enfin, jetez un œil à la méthode
ResetToLastCheckpoint
; pour comprendre ce qu'il fait, regardez le labyrinthe. Il y a des points dans le labyrinthe appelés
checkpoint . Pour passer le labyrinthe, le bot doit se rendre au point de contrĂ´le
vert .
Lorsqu'un bot marche sur un nouveau point de contrĂ´le, il devient le
dernier pour lui.
ResetToLastCheckpoint
réinitialise la position du bot, le déplaçant vers le dernier point de contrôle.
Bien que nous ne puissions pas utiliser ces méthodes, nous le corrigerons bientôt. Pour commencer, vous devez vous renseigner sur le modèle de conception de
commande .
Qu'est-ce que le modèle de conception de commande?
Le modèle de commande est l'un des 23 modèles de conception décrits dans le livre
Design Patterns: Elements of Reusable Object-Oriented Software écrit par le Gang of Four par Erich Gamma, Richard Helm, Ralph Johnson et John Vlissides (
GoF , Gang of Four).
Les auteurs rapportent que «le modèle de commande encapsule la demande en tant qu'objet, nous permettant ainsi de paramétrer d'autres objets avec des demandes différentes, des demandes de file d'attente ou de journal et de prendre en charge des opérations réversibles».
Ouah! Comment est-ce?
Je comprends que cette définition n'est pas très simple, alors analysons-la.
L'encapsulation signifie qu'un appel de méthode peut être encapsulé en tant qu'objet.
La méthode encapsulée peut affecter de nombreux objets en fonction du paramètre d'entrée. C'est ce qu'on appelle le
paramétrage d' autres objets.
La «commande» résultante peut être enregistrée avec d'autres équipes jusqu'à ce qu'elle soit exécutée. Il s'agit de la
file d'attente des demandes.
File d'attente d'équipeEnfin, la
réversibilité signifie que les opérations peuvent être annulées à l'aide de la fonction Annuler.
OK, mais comment cela se reflète-t-il dans le code?
La classe
Command aura une méthode
Execute , qui reçoit en paramètre d'entrée l'objet (par lequel la commande est exécutée) appelé
Receiver . En fait, la méthode Execute est
encapsulée par la classe Command.
De nombreuses instances de la classe Command peuvent être passées en tant qu'objets ordinaires, c'est-à -dire qu'elles peuvent être stockées dans des structures de données telles qu'une file d'attente, une pile, etc.
Pour exécuter une commande, vous devez appeler sa méthode Execute. La classe qui démarre l'exécution est appelée
Invoker .
Le projet contient actuellement une classe vide appelée
BotCommand
. Dans la section suivante, nous allons implémenter l'implémentation de ce qui précède pour permettre au bot d'effectuer des actions à l'aide du modèle de commande.
Déplacez le bot
Implémentation du modèle de commande
Dans cette section, nous implémentons le modèle de commande. Il existe de nombreuses façons de le mettre en œuvre. Dans ce tutoriel, nous allons couvrir l'un d'entre eux.
Pour commencer, allez dans
RW / Scripts et ouvrez le script
BotCommand dans l'éditeur. La classe
BotCommand
toujours vide, mais pas pour longtemps.
Insérez le code suivant dans la classe:
Que se passe-t-il ici?
- La variable
commandName
utilisée simplement pour stocker le nom de commande lisible par l'homme. Il n'est pas nécessaire de l'utiliser dans le modèle, mais nous en aurons besoin plus tard dans le tutoriel. - Le constructeur de
BotCommand
reçoit une fonction et une chaîne. Cela nous aidera à configurer la méthode Execute
de l'objet Command et son name
. - Le délégué
ExecuteCallback
définit le type de méthode encapsulée. La méthode encapsulée retournera vide et acceptera comme paramètre d'entrée un objet de type Bot
(composant Bot ). - La propriété
Execute
fera référence à la méthode encapsulée. Nous l'utiliserons pour appeler la méthode encapsulée. - La méthode
ToString
est remplacée pour renvoyer la chaîne commandName
. C'est pratique, par exemple, pour une utilisation dans l'interface utilisateur.
Enregistrez les modifications et c'est tout! Nous avons réussi à implémenter le modèle de commande.
Reste Ă l'utiliser.
Constitution d'équipe
Ouvrez le
BotInputHandler dans le dossier
RW / Scripts .
Ici, nous allons créer cinq instances de
BotCommand
. Ces instances encapsuleront des méthodes pour déplacer le GameObject Bot vers le haut, le bas, la gauche et la droite, ainsi que pour le tir.
Pour implémenter cela, insérez ce qui suit dans cette classe:
Dans chacune de ces instances,
une méthode anonyme est transmise au constructeur. Cette méthode anonyme sera encapsulée dans l'objet de commande correspondant. Comme vous pouvez le voir, la signature de chacune des méthodes anonymes répond aux exigences spécifiées par le délégué
ExecuteCallback
.
De plus, le deuxième paramètre du constructeur est une chaîne indiquant le nom de la commande. Ce nom sera retourné par la méthode
ToString
de l'instance de commande. Plus tard, nous l'appliquerons pour l'interface utilisateur.
Dans les quatre premières instances, les méthodes anonymes appellent la méthode
Move
sur l'objet
bot
. Cependant, leurs paramètres d'entrée sont différents.
Les commandes
MoveUp
,
MoveDown
,
MoveLeft
et
MoveRight
transmettent les paramètres de
Move
CardinalDirection.Up
,
CardinalDirection.Down
,
CardinalDirection.Left
et
CardinalDirection.Right
. Comme mentionné dans la section
Qu'est-ce que le modèle de conception de commande , ils indiquent différentes directions de déplacement du Bot GameObject.
Dans la cinquième instance, la méthode anonyme appelle la méthode
Shoot
pour l'objet
bot
. Grâce à cela, le bot tirera un obus lors de l'exécution de la commande.
Maintenant que nous avons créé les commandes, nous devons en quelque sorte y accéder lorsque l'utilisateur fait une entrée.
Pour ce faire,
BotInputHandler
code suivant dans le
BotInputHandler
, immédiatement après les instances de commande:
public static BotCommand HandleInput() { if (Input.GetKeyDown(KeyCode.W)) { return MoveUp; } else if (Input.GetKeyDown(KeyCode.S)) { return MoveDown; } else if (Input.GetKeyDown(KeyCode.D)) { return MoveRight; } else if (Input.GetKeyDown(KeyCode.A)) { return MoveLeft; } else if (Input.GetKeyDown(KeyCode.F)) { return Shoot; } return null; }
La méthode
HandleInput
renvoie une instance de la commande en fonction de la touche enfoncée par l'utilisateur. Enregistrez vos modifications avant de continuer.
Application de commandes
Super, maintenant il est temps d'utiliser les Ă©quipes que nous avons créées. AccĂ©dez Ă nouveau Ă
RW / Scripts et ouvrez le script
SceneManager dans l'éditeur. Dans cette classe, vous remarquerez un lien vers une variable
uiManager
de type
UIManager
.
La classe
UIManager
fournit des méthodes d'aide utiles pour l'
interface utilisateur du
terminal que nous utilisons dans cette scène. Si la méthode d'
UIManager
est utilisée, alors le tutoriel expliquera ce qu'elle fait, mais en général pour nos besoins il n'est pas nécessaire de connaître sa structure interne.
De plus, la variable
bot
fait référence au composant bot attaché au GameObject
Bot .
Ajoutez maintenant le code suivant Ă la classe
SceneManager
, en le remplaçant par comment
//1
:
Wow, combien de code! Mais ne vous inquiétez pas; nous sommes enfin prêts pour le premier vrai lancement du projet dans la fenêtre de jeu.
J'expliquerai le code plus tard. N'oubliez pas d'enregistrer les modifications.
Exécution du jeu pour tester le modèle de commande
Il est donc temps de construire; Cliquez sur
Lecture dans l'éditeur Unity.
Vous devriez pouvoir entrer des commandes de déplacement à l'aide des
touches WASD . Pour entrer la commande de prise de vue, appuyez sur la touche
F. Pour exécuter des commandes, appuyez sur
Entrée .
Remarque : tant que le processus d'exécution n'est pas terminé, la saisie de nouvelles commandes n'est pas possible.
Notez que des lignes sont ajoutées à l'interface utilisateur du terminal. Les équipes dans l'interface utilisateur sont indiquées par leurs noms. Ceci est rendu possible grâce à la variable
commandName
.
Notez également comment l'interface utilisateur défile vers le haut avant l'exécution et comment les lignes sont supprimées pendant l'exécution.
Nous étudions les équipes de plus près
Il est temps d'apprendre le code que nous avons ajouté dans la section "Application des commandes":
- La liste
botCommands
stocke des liens vers des instances de BotCommand
. N'oubliez pas que pour économiser de la mémoire, nous ne pouvons créer que cinq instances de commandes, mais il peut y avoir plusieurs références à une commande. De plus, la variable executeCoroutine
fait référence à ExecuteCommandsRoutine
, qui contrôle l'exécution de la commande. Update
vérifie si l'utilisateur a appuyé sur la touche Entrée; si c'est le cas, il appelle ExecuteCommands
, sinon CheckForBotCommands
est CheckForBotCommands
.CheckForBotCommands
utilise la méthode statique HandleInput
du BotInputHandler
pour vérifier si l'utilisateur a terminé l'entrée, et si c'est le cas, la commande est renvoyée . La commande retournée est transmise à AddToCommands
. Cependant, si les commandes sont exécutées, c'est-à -dire si executeRoutine
pas null, il reviendra sans rien transmettre Ă AddToCommands
. Autrement dit, l'utilisateur doit attendre la fin.AddToCommands
ajoute un nouveau lien vers l'instance retournée de la commande dans botCommands
.- La méthode
InsertNewText
de la classe InsertNewText
ajoute une nouvelle ligne de texte à l'interface utilisateur du terminal. Une chaîne de texte est une chaîne passée en paramètre d'entrée. Dans ce cas, nous lui passons commandName. - La méthode
ExecuteCommandsRoutine
lance ExecuteCommandsRoutine
. ResetScrollToTop
de UIManager
défiler l'interface utilisateur du terminal vers le haut. Cela se fait juste avant le début de l'exécution.ExecuteCommandsRoutine
contient une boucle for
qui itère sur les commandes à l'intérieur de la liste botCommands
et les exécute une par une, en passant l'objet bot
à la méthode renvoyée par la propriété Execute
. Après chaque exécution, une pause est ajoutée en secondes CommandPauseTime
.- La méthode
RemoveFirstTextLine
de UIManager
supprime la toute première ligne de texte dans l'interface utilisateur du terminal, si elle existe. Autrement dit, lorsqu'une commande est exécutée, son nom est supprimé de l'interface utilisateur. - Une fois toutes les commandes
botCommands
est effacé et le bot est réinitialisé au dernier point d'arrêt à l'aide de ResetToLastCheckpoint
. À la fin, executeRoutine
null
et l'utilisateur peut continuer Ă entrer des commandes.
Implémentation des fonctionnalités d'annulation et de rétablissement
Exécutez à nouveau la scène et essayez d'atteindre le point de contrôle vert.
Vous remarquerez que nous ne pouvons pas annuler la commande entrée. Cela signifie que si vous faites une erreur, vous ne pouvez pas revenir en arrière avant d'avoir terminé toutes les commandes que vous entrez. Vous pouvez résoudre ce problème en ajoutant les fonctionnalités
Annuler et
Rétablir .
Revenez Ă
SceneManager.cs et ajoutez la déclaration de variable suivante immédiatement après la déclaration
List pour
botCommands
:
private Stack<BotCommand> undoStack = new Stack<BotCommand>();
La variable
undoStack
est une
pile (de la famille Collections) qui stockera toutes les références aux commandes qui peuvent être annulées.
Nous ajoutons maintenant deux méthodes
UndoCommandEntry
et
RedoCommandEntry
qui exécuteront Undo et Redo. Dans la classe
SceneManager
,
SceneManager
code suivant après
ExecuteCommandsRoutine
:
private void UndoCommandEntry() {
Analysons le code:
- Si des commandes sont exécutées ou si la liste
botCommands
vide, la méthode UndoCommandEntry
rien. Sinon, il écrit un lien vers la dernière commande entrée dans la pile undoStack
. Cela supprime également le lien vers la commande de la liste botCommands
. - La méthode
RemoveLastTextLine
de UIManager
supprime la dernière ligne de texte de l'interface utilisateur du terminal afin que l'interface utilisateur corresponde au contenu de botCommands
. - Si la pile
undoStack
vide, alors RedoCommandEntry
ne fait rien. Sinon, il extrait la dernière commande du haut de undoStack
et l'ajoute Ă la liste des AddToCommands
utilisant AddToCommands
.
Nous allons maintenant ajouter une entrée au clavier pour utiliser ces fonctions. Dans la classe
SceneManager
remplacez le corps de la méthode
Update
par le code suivant:
if (Input.GetKeyDown(KeyCode.Return)) { ExecuteCommands(); } else if (Input.GetKeyDown(KeyCode.U))
- Lorsque vous appuyez sur la touche U , la méthode
UndoCommandEntry
est UndoCommandEntry
. - Lorsque vous appuyez sur la touche R , la méthode
RedoCommandEntry
est RedoCommandEntry
.
Gestion des cas de bord
Super, nous avons presque fini! Mais d'abord, nous devons faire ce qui suit:
- Lorsque vous entrez une nouvelle commande, la pile
undoStack
doit être effacée. - Avant d'exécuter des commandes, la pile
undoStack
doit être effacée.
Pour implĂ©menter cela, nous devons d'abord ajouter une nouvelle mĂ©thode Ă
SceneManager
. Insérez la méthode suivante après
CheckForBotCommands
:
private void AddNewCommand(BotCommand botCommand) { undoStack.Clear(); AddToCommands(botCommand); }
Cette méthode efface
undoStack
et appelle ensuite la méthode
AddToCommands
.
Remplacez maintenant l'appel Ă
AddToCommands
dans
CheckForBotCommands
par le code suivant:
AddNewCommand(botCommand);
Insérez ensuite la ligne suivante après l'
if
dans la méthode
ExecuteCommands
à effacer avant d'exécuter les commandes
undoStack
:
undoStack.Clear();
Et nous avons enfin terminé!
Sauvegardez votre travail. Générez le projet et cliquez sur dans l'éditeur de
lecture . Saisissez les commandes comme précédemment. Appuyez sur
U pour annuler les commandes. Appuyez sur
R pour répéter les commandes annulées.
Essayez d'arriver au point de contrĂ´le vert.
OĂą aller ensuite?
Pour en savoir plus sur les modèles de conception utilisés dans la programmation de jeux, je vous recommande d'étudier les
modèles de programmation de jeux de Robert Nystrom.
Pour en savoir plus sur les techniques C # avancées, suivez le cours
Collections C #, Lambdas et LINQ .
Tâche
En tant que tâche, essayez d'arriver au point de contrôle vert à la fin du labyrinthe. J'ai caché une des solutions sous le spoiler.
Solution- moveUp Ă— 2
- moveRight Ă— 3
- moveUp Ă— 2
- moveLeft
- tirer
- moveLeft Ă— 2
- moveUp Ă— 2
- moveLeft Ă— 2
- moveDown Ă— 5
- moveLeft
- tirer
- moveLeft
- moveUp Ă— 3
- tirer Ă— 2
- moveUp Ă— 5
- moveRight Ă— 3