Il y a différentes façons de créer un jeu en particulier. Habituellement, un développeur choisit quelque chose qui correspond à ses compétences, en utilisant les techniques qu'il connaît déjà pour produire le meilleur résultat possible. Parfois, les gens ne savent pas encore qu'ils ont besoin d'une certaine technique, peut-être même plus facile et meilleure, simplement parce qu'ils connaissent déjà un moyen de créer ce jeu..
Dans cette série de didacticiels, vous apprendrez à créer une intelligence artificielle pour un match de hockey en utilisant une combinaison de techniques, telles que les comportements de direction, que j'ai déjà expliquées en tant que concepts..
Remarque: Bien que ce tutoriel ait été écrit avec AS3 et Flash, vous devriez pouvoir utiliser les mêmes techniques et concepts dans presque tous les environnements de développement de jeux..
Le hockey est un sport amusant et populaire. En tant que jeu vidéo, il intègre de nombreux thèmes tels que les schémas de déplacement, le travail d’équipe (attaque, défense), l’intelligence artificielle et la tactique. Un jeu de hockey jouable est un excellent moyen de démontrer la combinaison de certaines techniques utiles.
Simuler le mécanicien de hockey, avec les athlètes qui courent et se déplacent, est un défi. Si les modèles de mouvement sont prédéfinis, même avec des chemins différents, le jeu devient prévisible (et ennuyeux). Comment pouvons-nous mettre en place un environnement aussi dynamique tout en gardant le contrôle de ce qui se passe? La réponse est: utiliser des comportements de direction.
Les comportements de direction visent à créer des modèles de mouvement réalistes avec une navigation d'improvisation. Elles sont basées sur des forces simples qui sont combinées à chaque mise à jour du jeu. Elles sont donc extrêmement dynamiques par nature. Cela en fait le choix idéal pour mettre en œuvre quelque chose d'aussi complexe et dynamique que le hockey ou le football.
Par souci de temps et d’enseignement, réduisons un peu la portée du jeu. Notre jeu de hockey ne suivra qu'un petit ensemble des règles originales du sport: dans notre jeu, il n'y aura pas de pénalités ni de gardiens de but, afin que chaque athlète puisse se déplacer sur la patinoire:
Partie de hockey utilisant des règles simplifiées.Chaque but sera remplacé par un petit "mur" sans filet. Pour marquer, une équipe doit déplacer le palet (le disque) pour le faire toucher n’importe quel côté du but de l’adversaire. Lorsque quelqu'un marque, les deux équipes se réorganisent et la rondelle est placée au centre. le match reprendra quelques secondes plus tard.
En ce qui concerne la manipulation de la rondelle: si un athlète, dites A, a la rondelle et est touché par un adversaire, dites B, B remporte la rondelle et A devient inamovible pendant quelques secondes. Si la rondelle quitte la patinoire, elle sera immédiatement placée au centre de la patinoire..
Je vais utiliser le moteur de jeu Flixel pour m'occuper de la partie graphique du code. Cependant, le code moteur sera simplifié ou omis dans les exemples afin de rester concentré sur le jeu lui-même..
Commençons par l’environnement de jeu, composé d’une patinoire, de plusieurs athlètes et de deux buts. La patinoire est composée de quatre rectangles placés autour de la zone de glace; ces rectangles vont entrer en collision avec tout ce qui les touche, donc rien ne quittera la zone de glace.
Un athlète sera décrit par le Athlète
classe:
public class Athlete private var mBoid: Boid; // contrôle le comportement de la commande private private var mId: int; // identifiant unique de la fonction publique athelete Athlete (thePosX: Number, thePosY: Number, theTotalMass: Number) mBoid = new Boid (thePosX, thePosY, theTotalMass); public function update (): void // Efface toutes les forces de direction mBoid.steering = null; // Flâne dans wanderInTheRink (); // Met à jour tous les éléments de direction mBoid.update (); fonction privée wanderInTheRink (): void var aRinkCenter: Vector3D = getRinkCenter (); // Si la distance du centre est supérieure à 80, // retourne au centre, sinon continuez à vous promener. if (Utils.distance (this, aRinkCenter)> = 80) mBoid.steering = mBoid.steering + mBoid.seek (aRinkCenter); else mBoid.steering = mBoid.steering + mBoid.wander ();
La propriété mBoid
est un exemple de Boid
class, une encapsulation de la logique mathématique utilisée dans la série de comportements de direction. le mBoid
Cette instance contient, entre autres éléments, des vecteurs mathématiques décrivant la direction actuelle, la force de direction et la position de l'entité..
le mettre à jour()
méthode dans le Athlète
La classe sera invoquée à chaque mise à jour du jeu. Pour le moment, il n’efface que les forces de direction actives, ajoute une force de dérapage et appelle finalement mBoid.update ()
. La commande précédente met à jour toute la logique de comportement de direction encapsulée dans mBoid
, faire bouger l'athlète (en utilisant l'intégration d'Euler).
La classe de jeu, qui est responsable de la boucle de jeu, sera appelée PlayState
. Il a la patinoire, deux groupes d’athlètes (un groupe par équipe) et deux buts:
Classe publique PlayState private var mAthletes: FlxGroup; var privé mRightGoal: objectif; var mLeftGoal privé: objectif; public function create (): void // Ici tout est créé et ajouté à l'écran. écraser la fonction publique update (): void // Faire entrer la patinoire en collision avec les athlètes en collision (mRink, mAthletes); // S'assurer que tous les athlètes resteront à l'intérieur de la patinoire. applyRinkContraints (); fonction privée applyRinkContraints (): void // vérifie si les athlètes se trouvent dans les limites // de la patinoire.
En supposant qu'un seul athlète ait été ajouté au match, voici le résultat de tout ce qui s'est passé jusqu'à présent:
L'athlète doit suivre le curseur de la souris pour que le joueur puisse contrôler quelque chose. Puisque le curseur de la souris a une position sur l'écran, il peut être utilisé comme destination pour le comportement d'arrivée.
Le comportement à l’arrivée obligera l’athlète à rechercher la position du curseur, à ralentir la vitesse à mesure qu’il approche du curseur et à s’arrêter là..
dans le Athlète
classe, remplaçons la méthode errante par le comportement d'arrivée:
public class Athlete // (…) fonction publique update (): void // Efface toutes les forces de direction mBoid.steering = null; // L'athlète est contrôlé par le joueur, // donc suivez simplement le curseur de la souris. followMouseCursor (); // Met à jour tous les éléments de direction mBoid.update (); fonction privée followMouseCursor (): void var aMouse: Vector3D = getMouseCursorPosition (); mBoid.steering = mBoid.steering + mBoid.arrive (aMouse, 50);
Le résultat est un athlète qui peut le curseur de la souris. Puisque la logique de mouvement est basée sur les comportements de direction, les athlètes naviguent sur la patinoire de manière convaincante et fluide..
Utilisez le curseur de la souris pour guider l'athlète dans la démo ci-dessous:
La rondelle sera représentée par la classe Palet
. Les parties les plus importantes sont les mettre à jour()
méthode et la propriétaire
propriété:
classe publique Puck public var velocity: Vector3D; public var position: Vector3D; propriétaire privé de la var: athlète; // l'athlète qui porte actuellement la rondelle. fonction publique setOwner (theOwner: Athlete): void if (mOwner! = theOwner) mOwner = theOwner; vélocité = nulle; public function update (): void fonction publique get owner (): Athlete return mOwner;
Suivant la même logique de l'athlète, la rondelle mettre à jour()
Cette méthode sera invoquée à chaque mise à jour du jeu. le propriétaire
propriété détermine si la rondelle est en possession de tout athlète. Si propriétaire
est nul
, cela signifie que la rondelle est "libre" et qu'elle se déplacera, finissant par rebondir hors de la patinoire.
Si propriétaire
n'est pas nul
, cela signifie que la rondelle est portée par un athlète. Dans ce cas, il ignorera toutes les vérifications de collision et sera placé de force devant l'athlète. Ceci peut être réalisé en utilisant la technique de l'athlète rapidité
vecteur, qui correspond également à la direction de l'athlète:
le devant
le vecteur est une copie de l'athlète rapidité
vecteur, alors ils pointent dans la même direction. Après devant
est normalisé, il peut être mis à l'échelle par n'importe quelle valeur, 30
-pour contrôler à quelle distance le palet sera placé devant l'athlète.
Enfin, la rondelle position
reçoit l'athlète position
ajouté à devant
, placer la rondelle à la position désirée.
Ci-dessous le code pour tout cela:
public class Puck // (…) fonction privée placeAheadOfOwner (): void var future: Vector3D = mOwner.boid.velocity.clone (); à venir = normaliser (à venir) * 30; position = mOwner.boid.position + ahead; écrase la fonction publique update (): void if (mOwner! = null) placeAheadOfOwner (); // (…)
dans le PlayState
classe, un test de collision permet de vérifier si la rondelle chevauche un athlète. Si c'est le cas, l'athlète qui vient de toucher la rondelle devient son nouveau propriétaire. Le résultat est un palet qui "colle" à l'athlète. Dans la démo ci-dessous, amenez l'athlète à toucher le palet au centre de la patinoire pour le voir en action:
Il est temps de faire bouger la rondelle après avoir été touché par le bâton. Quel que soit l'athlète qui porte la rondelle, il suffit de calculer un nouveau vecteur de vitesse pour simuler un coup au bâton. Cette nouvelle vitesse déplacera le palet vers la destination souhaitée.
Un vecteur de vitesse peut être généré par un vecteur de position à partir d'un autre; le vecteur nouvellement généré ira ensuite d'une position à une autre. C'est exactement ce dont vous avez besoin pour calculer le nouveau vecteur vitesse du palet après un coup sûr:
Calcul de la nouvelle vitesse de la rondelle après un coup de bâton.Dans l'image ci-dessus, le point de destination est le curseur de la souris. La position actuelle du palet peut être utilisée comme point de départ, tandis que le point où le palet devrait être après avoir été touché par le bâton peut être utilisé comme point final..
Le pseudo-code ci-dessous montre la mise en œuvre de goFromStickHit ()
, une méthode dans le Palet
classe qui implémente la logique illustrée dans l'image ci-dessus:
public class Puck // (…) fonction publique goFromStickHit (theAthlete: Athlete, theDestination: Vector3D, theSpeed: Number = 160): void // placez la rondelle devant le propriétaire pour éviter des trajectoires inattendues // (par exemple, une rondelle heurtant le l'athlète qui vient de frapper) placeAheadOfOwner (); // Marque la rondelle comme étant libre (pas de propriétaire) setOwner (null); // Calcule la nouvelle vitesse du palet var new_velocity: Vector3D = theDestination - position; vélocité = normaliser (new_velocity) * theSpeed;
le new_velocity
vecteur va de la position actuelle de la rondelle à la cible (la destination
). Après cela, il est normalisé et mis à l'échelle par la vitesse
, qui définit la magnitude (longueur) de new_velocity
. En d'autres termes, cette opération définit la vitesse à laquelle le palet se déplacera de sa position actuelle à la destination. Enfin, la rondelle rapidité
le vecteur est remplacé par new_velocity
.
dans le PlayState
classe, la goFromStichHit ()
La méthode est invoquée chaque fois que le joueur clique sur l’écran. Lorsque cela se produit, le curseur de la souris est utilisé comme destination du hit. Le résultat est vu dans cette démo:
Jusqu'ici, nous n'avons eu qu'un seul athlète qui se déplaçait autour de la patinoire. Au fur et à mesure que de nouveaux athlètes s’ajoutent, l’intelligence artificielle doit être mise en œuvre pour que tous ces athlètes aient l’impression de vivre et de penser.
Pour ce faire, nous utiliserons une machine à états finis basée sur une pile (FSM basé sur une pile, en abrégé). Comme décrit précédemment, les FSM sont polyvalents et utiles pour implémenter l'IA dans les jeux..
Pour notre match de hockey, une propriété nommée mBrain
sera ajouté à la Athlète
classe:
public class Athlete // (…) var privé mBrain: StackFSM; // contrôle la fonction publique du matériel d'intelligence artificielle Athlete (thePosX: Number, thePosY: Number, theTotalMass: Number) // (…) mBrain = new StackFSM (); // (…)
Cette propriété est une instance de StackFSM
, une classe précédemment utilisée dans le tutoriel FSM. Il utilise une pile pour contrôler les états d'intelligence artificielle d'une entité. Chaque état est décrit comme une méthode. quand un état est poussé dans la pile, il devient le actif méthode et est appelé lors de chaque mise à jour du jeu.
Chaque État effectuera une tâche spécifique, telle que le déplacement de l'athlète vers la rondelle. Chaque État est responsable de se finir, ce qui signifie qu'il est responsable de se sortir de la pile.
L'athlète peut être contrôlé par le joueur ou par l'IA maintenant, donc le mettre à jour()
méthode dans le Athlète
La classe doit être modifiée pour vérifier cette situation:
public class Athlete // (…) fonction publique update (): void // Efface toutes les forces de direction mBoid.steering = null; if (mControlledByAI) // L'athlète est contrôlé par l'IA. Mettez le cerveau à jour (FSM) et // restez loin des murs de la patinoire. mBrain.update (); else // L'athlète est contrôlé par le joueur, suivez donc // le curseur de la souris. followMouseCursor (); // Met à jour tous les éléments de direction mBoid.update ();
Si l'IA est active, mBrain
est mis à jour, ce qui invoque la méthode d'état actuellement active, obligeant l'athlète à se comporter en conséquence. Si le joueur est en contrôle, mBrain
est ignoré tous ensemble et l'athlète bouge selon les instructions du joueur.
En ce qui concerne les états à pousser dans le cerveau: pour l'instant, implémentons-en seulement deux. Un État laissera un athlète se préparer pour un match; lors de la préparation du match, un athlète se mettra à sa place sur la patinoire et restera immobile, regardant la rondelle. L'autre État obligera simplement l'athlète à rester immobile et à regarder la rondelle.
Dans les prochaines sections, nous allons implémenter ces états.
Si l'athlète est dans le tourner au ralenti
état, il va arrêter de bouger et regarder la rondelle. Cet état est utilisé lorsque l'athlète est déjà en position dans la patinoire et attend que quelque chose se passe, comme le début du match..
L'état sera codé dans le Athlète
classe, sous la tourner au ralenti()
méthode:
public class Athlete // (…) function public Athlete (thePosX: Number, thePosY: Number, theTotalMass: Number, theTeam: FlxGroup) // (…) // Indique au cerveau que l'état actuel est 'inactif' mBrain.pushState (tourner au ralenti); fonction privée idle (): void var aPuck: Puck = getPuck (); stopAndlookAt (aPuck.position); fonction privée stopAndlookAt (thePoint: Vector3D): void mBoid.velocity = thePoint - mBoid.position; mBoid.velocity = normaliser (mBoid.velocity) * 0,01;
Comme cette méthode ne sort pas de la pile, elle restera active pour toujours. À l’avenir, cet État apparaîtra lui-même pour faire place à d’autres États, tels que attaque, mais pour l'instant ça fait l'affaire.
le stopAndStareAt ()
Cette méthode suit le même principe que celui utilisé pour calculer la vitesse de la rondelle après une frappe. Un vecteur de la position de l'athlète à la position de la rondelle est calculé par thePoint - mBoid.position
et utilisé comme nouveau vecteur de vitesse de l'athlète.
Ce nouveau vecteur de vitesse déplacera l'athlète vers la rondelle. Pour que l’athlète ne bouge pas, le vecteur est mis à l’échelle par 0,01
, "rétrécissant" sa longueur à presque zéro. Cela empêche l'athlète de bouger, mais lui permet de regarder la rondelle.
Si l'athlète est dans le prepareForMatch
il se déplacera progressivement vers sa position initiale. La position initiale est l'endroit où l'athlète devrait être juste avant le début du match. Puisque l'athlète doit s'arrêter à destination, le comportement d'arrivée peut être utilisé à nouveau:
public class Athlete // (…) var privé mInitialPosition: Vector3D; // la position dans la patinoire où l'athlète devrait être placé fonction publique Athlète (thePosX: Number, thePosY: Number, theTotalMass: Number, the Team: FlxGroup) // (…) mInitialPosition = new Vector3D (thePosX, thePosY); // Indique au cerveau que l'état actuel est 'inactif' mBrain.pushState (inactif); fonction privée prepareForMatch (): void mBoid.steering = mBoid.steering + mBoid.arrive (mInitialPosition, 80); // Est-ce que je suis à la position initiale? if (distance (mBoid.position, mInitialPosition) <= 5) // I'm in position, time to stare at the puck. mBrain.popState(); mBrain.pushState(idle); // (… )
L'État utilise le comportement d'arrivée pour amener l'athlète vers la position initiale. Si la distance entre l'athlète et sa position initiale est inférieure à 5
, cela signifie que l'athlète est arrivé à l'endroit désiré. Quand cela arrive, prepareForMatch
se dégage de la pile et pousse tourner au ralenti
, en faisant le nouvel état actif.
Ci-dessous est le résultat de l'utilisation d'un FSM basé sur une pile pour contrôler plusieurs athlètes. presse g
les placer à des positions aléatoires sur la patinoire, en poussant prepareForMatch
Etat:
Ce tutoriel a présenté les fondements de la mise en œuvre d'un jeu de hockey utilisant des comportements de direction et des machines à états finis basés sur des piles. En combinant ces concepts, un athlète peut se déplacer sur la patinoire en suivant le curseur de la souris. L'athlète peut aussi frapper la rondelle vers une destination..
En utilisant deux états et un FSM basé sur une pile, les athlètes peuvent se réorganiser et se rendre à leur position sur la patinoire en se préparant pour le match..
Dans le prochain tutoriel, vous apprendrez à faire attaquer les athlètes en portant la rondelle vers le but tout en évitant les adversaires..