Intelligence artificielle – jeux vidéo – mouvement

 

Suite de l’article Intelligence artificielle – jeux vidéo, vous allez passer aux choses sérieuses : la poursuite intelligente. Pour rappel, vous avez gérer l’affichage du labyrinthe avec ses murs et les 2 protagonistes matérialisé par le carré rouge pour le poursuivant et le carré jaune pour le poursuivi.

 

Petit préambule

Le labyrinthe est représenté sur un plan en 2 dimensions (2D) orienté comme ci-dessous.

IA_labyrinthe_orientation

Chaque élément (mur, ennemi, ou joueur) à afficher dans ce plan nécessite pour indiquer son emplacement, de spécifier ses coordonnées (abscisse et ordonnée) dans ce plan 2D.

 

Les protagonistes : des objets ?

Notre exemple met en scène un robot tueur qui poursuit un joueur. Du Berzerk tout craché.

Nous parlons donc d’au moins 2 protagonistes qui ont de fait une existence (certes virtuelle) propre : pourraient ils être représentés sous forme d’objets ?

Oui, oui, oui, et encore oui.

Cependant, un objet a du sens que s’il a des caractéristiques (les propriétés) et aussi des moyens pour agir (les méthodes).

Ce qui vient à l’esprit en premier lieu est bien entendu leur positionnement sur l’écran : abscisse x et ordonnée y.

Pour créer ces objets, rien de plus simple :

var enemy = {
 x : 0,
 y : 0
}

var player = {
 x : 0,
 y : 0
}

Il serait aussi bon que le positionnement de chacun soit initialisé à partir du tableau du jeu. Je vous propose donc de modifier la fonction initGameGrid en ce sens :

 var initGameGrid = function() {
  for(var i in walls)  {
   var line = walls[i];
   var element = line.split("");
   for(var j in element)  {
    if ( element[ j ] == "#" ) {
     showWall(gameCanvasContext,j,i);
    } else if ( element[ j ] == "E" ) {
     showEnemy(gameCanvasContext,j,i);
     enemy.x = parseInt(j);
     enemy.y = parseInt(i);
     element[ j ] = " ";
    } else if ( element[ j ] == "P" ) {
     showPlayer(gameCanvasContext,j,i);
     player.x = parseInt(j);
     player.y = parseInt(i);
     element[ j ] = " ";
    }
   }
  }
 }

Nous avons donc une bonne base pour faire du développement propre.

 

Choix de la direction à prendre

Je vous propose dans un premier temps que le poursuivant (enemy) cherche quelle direction il doit prendre pour atteindre sa cible (player). Cela prend la forme d’une fonction qui renverra une direction sur 4 possibles : N pour le nord, S pour le sud, E pour l’est, et W pour l’ouest.

On considère dans cet exemple que le nord est le haut de l’écran, le sud le bas, l’est la droite et l’ouest la gauche.

Le principe :
– calculer les distances verticale et horizontale qui séparent les 2 protagonistes;
– déterminer la direction à prendre sur chaque axe (vertical et horizontal);
– comparer les distances verticale et horizontale pour déterminer l’axe sur lequel l’enemy va se déplacer : on choisit l’axe sur lequel la distance est la plus grande.

La méthode ci-dessous prend en paramètres 2 objets enemy et player qui ont les 2 mêmes propriétés x et y indicatrices de leur positionnement sur l’écran.

Cette méthode est à rattacher à l’objet enemy puisqu’elle n’a de sens que pour lui, il est le seul poursuivant.

Le code :

var enemy = {
 x : 0,
 y : 0,
  
 currentDirection : null,

 searchDirectionToTarget : function(enemy, player) {

  var xDirection;
  var yDirection;   
  var xDist;
  var yDist;
   
  if ( enemy.x > player.x ) {
   xDist = enemy.x - player.x;
   xDirection  = "W";
  } else {
   xDist = player.x - enemy.x;
   xDirection  = "E";
  }
   
  if ( enemy.y > player.y ) {
   yDist = enemy.y - player.y;
   yDirection  = "N";
  } else {
   yDist = player.y - enemy.y;
   yDirection  = "S";
  }   
   
  if ( xDist > yDist ) {
   this.currentDirection = xDirection;
  } else {
   this.currentDirection = yDirection;
  }
   
 }
}

 

Se déplacer en fonction de la direction choisie

Ici, le déplacement est simple : la ligne droite jusqu’à la rencontre d’un obstacle.

N’oubliez pas l’orientation de notre labyrinthe : son origine est en haut à gauche (position x=0 et y=0):
– pour faire avancer vers la droite un mobile dans le labyrinthe, il faut changer sa position X en l’incrémentant;
– pour faire avancer vers la gauche un mobile dans le labyrinthe, il faut changer sa position X en la décrémentant;
– pour faire avancer vers le bas un mobile dans le labyrinthe, il faut changer sa position Y en l’incrémentant;
– pour faire avancer vers le haut un mobile dans le labyrinthe, il faut changer sa position Y en la décrémentant.

IA_labyrinthe_orientation

Vous allez donc soit incrémenter ou décrémenter la position du poursuivant sur l’axe choisi:
– si la direction à prendre est N, la position X du poursuivant va être décrémentée;
– si la direction à prendre est S, la position X du poursuivant va être incrémentée;
– si la direction à prendre est W, la position Y du poursuivant va être décrémentée;
– si la direction à prendre est E, la position Y du poursuivant va être incrémentée.

Je vous propose de procéder en trois temps :
enemy détermine son pas de déplacement en fonction de la position du joueur;
enemy se déplacer dans le labyrinthe;
– on matérialise son déplacement en l’affichant à l’écran.

Le pas de déplacement se fait en fonction de la direction à prendre stockée dans la propriété currentDirection.

Le pas de déplacement se fait verticalement ou horizontalement. Il y a donc 2 valeurs à renseigner.

Matérialisez ces 2 valeurs par les propriétés xStep et yStep qui sont respectivement les pas de déplacement horizontal et vertical.

La pas de déplacement peut prendre 3 valeurs :
– horizontal xStep : 0 ne bouge pas, 1 avance vers la droite, -1 recule vers la gauche;
– vertical yStep : 0 ne bouge pas, 1 avance vers le bas, -1 recule vers le haut.

J’utilise les termes reculer et avancer qui résultent simplement de l’orientation du repère utilisé : origine en haut à gauche, orienté vers la droite et le bas.

L’enemy se déplace horizontalement ou verticalement, pas les 2 à fois, il en résulte :
– lorsque xStep est différent de 0 yStep prend la valeur 0;
– lorsque yStep est différent de 0 xStep prend la valeur 0.

Le code :

setDSPStepFromCurrentDirection : function() {
 switch ( this.currentDirection ) {
  case "E":
   this.xStep = 1;
   this.yStep = 0;
   break;
	  
  case "W":
   this.xStep = -1;
   this.yStep = 0;
   break;
	  
  case "N":
   this.xStep = 0;
   this.yStep = -1;
   break;
	  
  case "S":
   this.xStep = 0;
   this.yStep = 1;
   break;
	  
  default:
   this.xStep = 0;
   this.yStep = 0;	 
 }	
},

Maintenant il faut déplacer l’enemy dans le labyrinthe pour que cela se voit. Commencez par modifier les coordonnées d’enemy :

moveDSPToTarget : function() {
 this.x += this.xStep;
 this.y += this.yStep;
}

Enfin affichons le via la fonction showEnemy :

showEnemy(gameCanvasContext,enemy.x,enemy.y); 

Le résultat : un deuxième carré rouge de l’ennemi (l’écran n’a pas été rafraichi, du coup l’ancienne position est toujours affichée).

IA_labyrinthe_2

Dans le prochain article Intelligence artificielle jeux vidéo – éviter les obstacles, vous apprendrez à gérer les obstacles (murs) rencontrés par enemy.

 

Des remarques, des améliorations, des idées, des coquilles : dites le. Faites savoir si cet article vous a été utile par un commentaire ou les réseaux sociaux.

Le code complet :

<html>
 <body>
  <div id="gameDiv" style="margin-left:auto;margin-right:auto;width:1168px;height:740px;">
  </div>
 </body>
 <script>
(function() {
 
 var enemy = {
  x : 0,
  y : 0,
  
  currentDirection : null,
  xStep : 0,
  yStep : 0,
  
  searchDirectionToTarget : function(enemy, player) {
   var xDirection;
   var yDirection;   
   var xDist;
   var yDist;
   
   if ( enemy.x > player.x ) {
    xDist = enemy.x - player.x;
    xDirection  = "W";
   } else {
    xDist = player.x - enemy.x;
    xDirection  = "E";
   }
   
   if ( enemy.y > player.y ) {
    yDist = enemy.y - player.y;
    yDirection  = "N";
   } else {
    yDist = player.y - enemy.y;
    yDirection  = "S";
   }   
   
   if ( xDist > yDist ) {
    this.currentDirection = xDirection;
   } else {
    this.currentDirection = yDirection;
   }   
 },
  
  setDSPStepFromCurrentDirection : function() {
   switch ( this.currentDirection ) {
    case "E":
     this.xStep = 1;
     this.yStep = 0;
     break;
	  
    case "W":
     this.xStep = -1;
     this.yStep = 0;
     break;
	  
    case "N":
     this.xStep = 0;
     this.yStep = -1;
     break;
	  
    case "S":
     this.xStep = 0;
     this.yStep = 1;
     break;
	  
    default:
     this.xStep = 0;
     this.yStep = 0;	 
   }	
  },
  
  moveDSPToTarget : function() {
   this.x += this.xStep;
   this.y += this.yStep;
  }

 };
 
 var player = {
   x : 0,
   y : 0
 };
 
 var createCanvasContext = function(name, width, height, zindex, htmlElement, color) {
  var canvas = window.document.createElement("canvas");
  canvas.id = name;
  canvas.style.position = "absolute";
  if ( color != undefined )
   canvas.style.background = color;
  canvas.style.zIndex = zindex;
  canvas.width = width;
  canvas.height = height;
  if ( htmlElement != undefined )
   htmlElement.appendChild(canvas.getContext('2d').canvas);
  return canvas.getContext('2d');
 }
 
 var showSquare = function(canvasContext,color,x,y) {
  canvasContext.beginPath();
  canvasContext.rect(x*10,y*10,10,10);
  canvasContext.fillStyle = color;
  canvasContext.fill();
  canvasContext.stroke();
 }
 
 var showWall = function(canvasContext,x,y) {
  showSquare(canvasContext,"grey",x,y);
 }
 
 var showPlayer = function(canvasContext,x,y) {
  showSquare(canvasContext,"yellow",x,y);
 }
 
 var showEnemy = function(canvasContext,x,y) {
  showSquare(canvasContext,"red",x,y);
 }
 
 var initGameGrid = function() {
  for(var i in walls)  {
   var line = walls[i];
   var element = line.split("");
   for(var j in element)  {
    if ( element[ j ] == "#" ) {
     showWall(gameCanvasContext,j,i);
    } else if ( element[ j ] == "E" ) {
     showEnemy(gameCanvasContext,j,i);
	 enemy.x = parseInt(j);
	 enemy.y = parseInt(i);
     element[ j ] = " ";
    } else if ( element[ j ] == "P" ) {
     showPlayer(gameCanvasContext,j,i);
	 player.x = parseInt(j);
	 player.y = parseInt(i);
     element[ j ] = " ";
    }
   }
  }
 }

 var walls = {
  0:  "                    ",
  1:  "              #     ",
  2:  "              #     ",
  3:  "                    ",
  4:  "                    ",
  5:  "                    ",
  6:  "              #     ",
  7:  "         #    #     ",
  8:  "         #    #     ",
  9:  "  E           #  P  ",
  10: "              #     ",
  11: "         #    #     ",
  12: "         #    #     ",
  13: "         #    #     ",
  14: "                    ",
  15: "                    ",
  16: "                    ",
  17: "              #     ",
  18: "              #     ",
  19: "                    "
 }
 
 var gameDiv = document.getElementById("gameDiv");
 var gameCanvasContext = createCanvasContext("game",200,200,1,gameDiv,"#000000");
 
 // read and display game grid
 initGameGrid();
 enemy.searchDirectionToTarget(enemy,player);
 enemy.setDSPStepFromCurrentDirection();
 enemy.moveDSPToTarget();
 showEnemy(gameCanvasContext,enemy.x,enemy.y); 

})();
 </script>
</html>

 

Posté dans html5Taggé intelligence artificielle, intelligence artificielle jeu vidéo, jeu vidéo javascript  |  3 commentaires

3 réponses à "Intelligence artificielle – jeux vidéo – mouvement"

Répondre