Créer une IA de jeu au hockey à l'aide de comportements de direction mécanismes de jeu

Dans les articles précédents de cette série, nous nous sommes concentrés sur la les concepts derrière l'intelligence artificielle, nous avons appris. Dans cette partie, nous intégrerons toute la mise en œuvre dans un jeu de hockey entièrement jouable. Vous apprendrez comment ajouter les éléments manquants nécessaires à la transformation en un jeu, tels que le score, les bonus et un peu de conception de jeu..

Résultat final

Ci-dessous, le jeu qui sera implémenté en utilisant tous les éléments décrits dans ce tutoriel..

Conception de jeu de réflexion

Les parties précédentes de cette série avaient pour objectif d'expliquer le fonctionnement de l'intelligence artificielle dans le jeu. Chaque partie détaille un aspect particulier du jeu, comme la manière dont les athlètes se déplacent et la manière dont l'attaque et la défense sont mises en œuvre. Ils étaient basés sur des concepts tels que les comportements de pilotage et les machines à états finis basées sur des piles..

Pour faire un jeu entièrement jouable, cependant, tous ces aspects doivent être intégrés à un noyau mécanicien de jeu. Le choix le plus évident serait de mettre en œuvre toutes les règles officielles d'un match de hockey officiel, mais cela nécessiterait beaucoup de travail et de temps. Prenons une approche plus simple de la fantasy.

Toutes les règles de hockey seront remplacées par une seule: si vous portez la rondelle et que vous êtes touché par un adversaire, vous êtes bloqué et éclaté en un million de pièces! Cela rendra le jeu plus simple et plus amusant pour les deux joueurs: celui qui porte la rondelle et celui qui tente de la récupérer.

Afin d'améliorer cette mécanique, nous ajouterons quelques bonus. Ils aideront le joueur à marquer et à rendre le jeu un peu plus dynamique.

Ajouter la capacité de marquer

Commençons par le système de notation, chargé de déterminer qui gagne ou perd. Une équipe marque à chaque fois que la rondelle entre dans le but adverse.

Le moyen le plus simple de réaliser cela consiste à utiliser deux rectangles superposés:

Des rectangles superposés décrivant la zone de l'objectif. Si la rondelle entre en collision avec le rectangle rouge, l’équipe marque.

Le rectangle vert représente la surface occupée par la structure de l’objectif (le cadre et le filet). Cela fonctionne comme un bloc solide, de sorte que la rondelle et les athlètes ne pourront pas passer à travers; ils vont rebondir.

Le rectangle rouge représente la "zone de score". Si la rondelle chevauche ce rectangle, cela signifie qu'une équipe vient de marquer.

Le rectangle rouge est plus petit que le vert et est placé devant. Ainsi, si la rondelle touche le but de n'importe quel côté sauf de l'avant, elle rebondira et aucun point ne sera ajouté:

Quelques exemples du comportement de la rondelle si elle touchait les rectangles en se déplaçant.

Tout organiser après les scores de quelqu'un

Après le pointage de l’équipe, tous les athlètes doivent revenir à leur position initiale et la rondelle doit être replacée au centre de la patinoire. Après ce processus, le match peut continuer.

Déplacer les athlètes vers leur position initiale

Comme expliqué dans la première partie de cette série, tous les athlètes ont un état d'IA appelé prepareForMatch qui les déplacera vers la position initiale et les fera s'arrêter en douceur.

Lorsque la rondelle chevauche une des "zones de score", tout état de l'IA actuellement actif de tous les athlètes est supprimé et prepareForMatch est poussé dans le cerveau. Où que se trouvent les athlètes, ils reviendront à leur position initiale après quelques secondes:

Déplacement de la caméra vers le centre de la patinoire

Étant donné que la caméra suit toujours la rondelle, si elle est directement téléportée vers le centre de la patinoire après que quelqu'un ait marqué, la vue actuelle changera brusquement, ce qui serait laide et déroutant..

Une meilleure façon de faire est de déplacer la rondelle en douceur vers le centre de la patinoire; puisque la caméra suit la rondelle, la vue se déplacera gracieusement du but au centre de la patinoire. 

Cela peut être réalisé en modifiant le vecteur de vitesse du puck après avoir touché une zone de but. Le nouveau vecteur de vitesse doit "pousser" la rondelle vers le centre de la patinoire, de sorte qu'elle puisse être calculée comme suit:

var c: Vector3D = getRinkCenter (); var p: Vector3D = puck.position; var v: Vector3D = c - p; v = normaliser (v) * 100; puck.velocity = v;

En soustrayant la position du centre de la patinoire de la position actuelle de la rondelle, il est possible de calculer un vecteur qui pointe directement vers le centre de la patinoire.

Après normalisation de ce vecteur, il peut être mis à l’échelle par toute valeur, comme 100, qui contrôle la rapidité avec laquelle la rondelle se déplace vers le centre de la patinoire.

Ci-dessous une image avec une représentation du nouveau vecteur de vitesse:

Calcul d'un nouveau vecteur de vitesse qui déplace la rondelle vers le centre de la patinoire.

Ce vecteur V est utilisé comme vecteur de vitesse de la rondelle, de sorte que la rondelle se déplace comme prévu vers le centre de la patinoire.

Pour éviter tout comportement étrange lorsque la rondelle se dirige vers le centre de la patinoire, par exemple lors d’une interaction avec un athlète, la rondelle est désactivée pendant le processus. En conséquence, il cesse d'interagir avec les athlètes et est marqué comme invisible. Le joueur ne verra pas le palet bouger, mais la caméra le suivra quand même..

Afin de décider si le palet est déjà en position, la distance entre celui-ci et le centre de la patinoire est calculée pendant le mouvement. Si c'est moins de dix, Par exemple, la rondelle est suffisamment proche pour être placée directement au centre de la patinoire et réactivée afin que le match puisse continuer.

Ajout de bonus

L'idée des power-ups est d'aider le joueur à atteindre l'objectif principal du jeu, qui est de marquer en portant le puck jusqu'au but de l'adversaire..

Par souci de portée, notre jeu n'aura que deux bonus: Ghost Aide et Peur la rondelle. Le premier ajoute trois athlètes supplémentaires à l'équipe du joueur pendant un certain temps, tandis que le second force les adversaires à fuir le puck pendant quelques secondes.

Des bonus sont ajoutés aux deux équipes lorsque quelqu'un marque.

Mise en œuvre de la mise en route de "Ghost Help"

Depuis tous les athlètes ajoutés par le Ghost Aide la mise sous tension sont temporaires, la Athlète la classe doit être modifiée pour permettre à un athlète d'être marqué comme "fantôme". Si un athlète est un fantôme, il se retirera du jeu après quelques secondes..

Ci-dessous est la Athlète classe, en soulignant uniquement les ajouts apportés pour prendre en charge la fonctionnalité fantôme:

public class Athlete // (…) private var mGhost: Boolean; // indique si l'athlète est un fantôme (un bonus qui ajoute de nouveaux athlètes pour aider à voler la rondelle). private var mGhostCounter: Number; // compte le temps pendant lequel un fantôme reste actif, fonction publique Athlete (thePosX: Number, thePosY: Number, theTotalMass: Number) // (…) mGhost = false; mGhostCounter = 0; // (…) public function setGhost (theStatus: Boolean, theDuration: Number): void mGhost = theStatus; mGhostCounter = theDuration;  fonction publique amIAGhost (): Boolean return mGhost;  public function update (): void // (…) // Met à jour les compteurs de puissance et remplit la tâche updatePowerups (); // (…) public function updatePowerups (): void // TODO. 

La propriété mGhost est un booléen qui indique si l'athlète est un fantôme ou non, alors que mGhostCounter contient le nombre de secondes que l'athlète doit attendre avant de se retirer du jeu.

Ces deux propriétés sont utilisées par le updatePups () méthode:

fonction privée updatePowerups (): void // Si l'athlète est un fantôme, il possède un compteur qui contrôle // le moment où il doit être supprimé. if (amIAGhost ()) mGhostCounter - = time_elapsed; si (mGhostCounter <= 2)  // Make athlete flicker when it is about to be removed. flicker(0.5);  if (mGhostCounter <= 0)  // Time to leave this world! (again) kill();   

le updatePups () méthode, appelée au sein de l'athlète mettre à jour() routine, gérera tous les processus de mise sous tension chez l’athlète. À l'heure actuelle, tout ce qu'il fait est de vérifier si l'athlète actuel est un fantôme ou non. Si c'est le cas, alors le mGhostCounter la propriété est décrémentée par le temps écoulé depuis la dernière mise à jour.

Quand la valeur de mGhostCounter atteint zéro, cela signifie que l'athlète temporaire est actif depuis assez longtemps, il doit donc se retirer du jeu. Pour que le joueur en soit conscient, l'athlète commencera à scintiller ses deux dernières secondes avant de disparaître..

Enfin, il est temps de mettre en œuvre le processus d’ajout d’athlètes temporaires lorsque la mise sous tension est activée. Cela se fait dans le powerupGhostHelp () méthode, disponible dans la logique de jeu principale:

fonction privée powerupGhostHelp (): void var aAthlete: Athlete; pour (var i: int = 0; i < 3; i++)  // Add the new athlete to the list of athletes aAthlete = addAthlete(RINK_WIDTH / 2, RINK_HEIGHT - 100); // Mark the athlete as a ghost which will be removed after 10 seconds. aAthlete.setGhost(true, 10);  

Cette méthode itère sur une boucle qui correspond à la quantité d’athlètes temporaires ajoutés. Chaque nouvel athlète est ajouté au bas de la patinoire et marqué comme un fantôme. 

Comme décrit précédemment, les athlètes fantômes se retireront du jeu..

Mise en œuvre du power-up "Fear The Puck"

le Peur la rondelle la puissance fait que tous les adversaires fuient la rondelle pendant quelques secondes. 

Comme le Ghost Aide mise sous tension, la Athlète La classe doit être modifiée pour accueillir cette fonctionnalité:

public class Athlete // (…) var privé mFearCounter: Number; // compte le temps pendant lequel l'athlète doit s'échapper de la rondelle (lorsque l'activation de la peur est active). fonction publique Athlete (thePosX: Number, thePosY: Number, theTotalMass: Number) // (…) mFearCounter = 0; // (…) fonction publique fearPuck (theDuration: Number = 2): void mFearCounter = theDuration;  // Retourne vrai si le mFearCounter a une valeur et que l'athlète // n'est pas inactif ou en train de préparer un match. fonction privée shouldIEvadeFromPuck (): Boolean return mFearCounter> 0 && mBrain.getCurrentState ()! = inactif && mBrain.getCurrentState ()! = prepareForMatch;  fonction privée updatePowerups (): void if (mFearCounter> 0) mFearCounter - = elapsed_time;  // (…) public function update (): void // (…) // Met à jour les compteurs de puissance et remplit la tâche updatePowerups (); // Si l'athlète est un adversaire contrôlé par l'IA si (amIAnAiControlledOpponent ()) // Vérifie si la mise sous tension "peur du puck" est active. // Si c'est vrai, évite la rondelle. if (shouldIEvadeFromPuck ()) evadeFromPuck ();  // (…) fonction publique evadeFromPuck (): void // TODO

Premièrement le updatePups () la méthode est modifiée pour décrémenter la mFearCounter propriété, qui contient la quantité de temps que l'athlète doit éviter la rondelle. le mFearCounter la propriété est modifiée à chaque fois que la méthode peurPuck () est appelé.

dans le Athlètede mettre à jour() méthode, un test est ajouté pour vérifier si la mise sous tension doit avoir lieu. Si l'athlète est un adversaire contrôlé par l'IA (amIAnAiControlledOpponent () résultats vrai) et l’athlète doit échapper à la rondelle (shouldIEvadeFromPuck () résultats vrai aussi), le evadeFromPuck () la méthode est invoquée.

le evadeFromPuck () La méthode utilise le comportement d'évitement, ce qui permet à une entité d'éviter tout objet et sa trajectoire:

fonction privée evadeFromPuck (): void mBoid.steering = mBoid.steering + mBoid.evade (getPuck (). getBoid ()); 

Tous les evadeFromPuck () La méthode consiste à ajouter une force d’évasion à la force de direction de l’athlète actuel. Cela lui fait échapper au puck sans ignorer les forces de pilotage déjà ajoutées, telles que celle créée par l'état de l'IA actuellement actif..

Pour être évitable, la rondelle doit se comporter comme un boid, comme le font tous les athlètes (plus d'informations à ce sujet dans la première partie de la série). Par conséquent, une propriété boid, qui contient la position et la vitesse actuelles de la rondelle, doit être ajoutée à la Palet classe:

class Puck // (…) private var mBoid: Boid; // (…) public function update () // (…) mBoid.update ();  fonction publique getBoid (): Boid return mBoid;  // (…)

Enfin, nous mettons à jour la logique principale du jeu pour que les adversaires aient peur du puck lorsque le power-up est activé:

fonction privée powerupFearPuck (): void var i: uint, athlètes: Array = rightTeam.members, taille: uint = athlètes.length; pour (i = 0; i < size; i++)  if (athletes[i] != null)  // Make athlete fear the puck for 3 seconds. athletes[i].fearPuck(3);   

La méthode itère sur tous les athlètes adverses (la bonne équipe, dans ce cas), en appelant le peurkpuck () méthode de chacun d'entre eux. Cela déclenchera la logique qui fait que les athlètes craignent la rondelle pendant quelques secondes, comme expliqué précédemment..

Geler et briser

Le dernier ajout au jeu est la partie qui gèle et se brise. Il est exécuté dans la logique de jeu principale, où une routine vérifie si les athlètes de l'équipe de gauche se chevauchent avec ceux de la bonne équipe..

Cette vérification de chevauchement est automatiquement effectuée par le moteur de jeu Flixel, qui appelle un rappel chaque fois qu'un chevauchement est détecté:

athlètes de fonction privéeOverlapped (theLeftAthlete: athlète, theRightAthlete: athlète): void // La rondelle a-t-elle un propriétaire? if (mPuck.owner! = null) // Oui, c'est le cas. if (mPuck.owner == theLeftAthlete) // Le propriétaire de Puck est l'athlète de gauche theLeftAthlete.shatter (); mPuck.setOwner (theRightAthlete);  else if (mPuck.owner == theRightAthlete) // Le propriétaire de Puck est le bon athlète theRightAthlete.shatter (); mPuck.setOwner (theLeftAthlete); 

Ce rappel reçoit comme paramètres les athlètes de chaque équipe qui se chevauchent. Un test vérifie si le propriétaire de la rondelle n'est pas nul, ce qui signifie qu'il est porté par quelqu'un.

Dans ce cas, le propriétaire de la rondelle est comparé aux athlètes qui viennent de se chevaucher. Si l'un d'eux porte le palet (il est donc le propriétaire du palet), il est brisé et la propriété du palet passe à l'autre athlète..

le briser() méthode dans le Athlète La classe marquera l’athlète comme étant inactive et la placera au bas de la patinoire après quelques secondes. Il émettra également plusieurs particules représentant des morceaux de glace, mais ce sujet sera traité dans un autre article..

Conclusion

Dans ce tutoriel, nous avons implémenté quelques éléments nécessaires pour transformer notre prototype de hockey en un jeu entièrement jouable. J'ai intentionnellement mis l'accent sur les concepts à la base de chacun de ces éléments, plutôt que sur la manière de les implémenter dans le moteur de jeu X ou Y.

L’approche «geler et écraser» utilisée pour le jeu peut sembler trop fantastique, mais elle permet de garder le projet gérable. Les règles sportives sont très spécifiques et leur mise en œuvre peut être délicate.

En ajoutant quelques écrans et des éléments HUD, vous pouvez créer votre propre jeu de hockey complet à partir de cette démo.!

Références

  • Patinoire: Stade de hockey sur GraphicRiver
  • Sprites: joueurs de hockey par Taylor J Glidden
  • Icons: Game-Icons de Lorc
  • Curseur de souris: Curseur par Iwan Gabovitch
  • Touches d'instructions: Pack clavier de Nicolae Berbece
  • Crosshair: Crosshairs Pack par Bryan
  • SFX / Musique: fracassement de Michel Baradari, mise au jeu et acclamation de gr8sfx, musique de DanoSongs.com