A * Pathfinding pour les joueurs de plate-forme basés sur une grille 2D différentes tailles de caractères

Dans ce didacticiel, nous allons étendre notre moteur de recherche de plateforme basé sur la grille afin qu’il puisse gérer les personnages qui occupent plus d’une cellule de la grille..

Si vous n'avez pas encore ajouté de support unidirectionnel à votre code, je vous le recommande, mais ce n'est pas nécessaire pour suivre ce tutoriel..

Démo

Vous pouvez jouer à la démonstration de Unity ou à la version WebGL (100 Mo +) pour voir le résultat final en action. Utilisation WASD déplacer le personnage, click gauche sur un endroit pour trouver un chemin que vous pouvez suivre pour vous y rendre, clic-droit une cellule pour basculer le sol à ce point, et cliquer et faire glisser les curseurs pour changer leurs valeurs.

Les caractères de taille différente doivent emprunter des chemins différents. L'algorithme mis à jour le reconnaît..

Position du personnage

Pathfinder accepte la position, la largeur et la hauteur du caractère en tant qu'entrée. Bien que la largeur et la hauteur soient faciles à interpréter, nous devons préciser à quel bloc les coordonnées de position se rapportent..

La position que nous passons doit être en termes de coordonnées de carte, ce qui signifie que, là encore, nous devons accepter une certaine imprécision. J'ai décidé qu'il serait judicieux de faire en sorte que la position se réfère à la tuile de caractère en bas à gauche, car elle correspond au système de coordonnées de la carte..

Positions des personnages pour un personnage 3x3. 

Avec cela clarifié, nous pouvons mettre à jour le Pathfinder.

Vérifier que l'objectif est réaliste

Premièrement, nous devons nous assurer que notre personnage de taille personnalisée peut tenir dans l'emplacement de destination. Jusqu'à présent, nous n'avons coché qu'un bloc pour le faire, car il s'agissait de la taille maximale (et unique) du caractère:

if (mGrid [end.x, end.y] == 0) renvoie null;

Maintenant, cependant, nous devons parcourir chaque cellule occupée par le personnage si elle se trouvait en position finale et vérifier si l'une d'entre elles est un bloc solide. Si tel est le cas, il est évident que le personnage ne peut pas rester là, donc l'objectif ne peut pas être atteint.

Pour ce faire, déclarons d’abord un booléen que nous définirons à faux si le personnage est dans une tuile solide et vrai autrement:

var inSolidTile = false;

Ensuite, nous allons parcourir chaque bloc du personnage:

pour (var w = 0; w < characterWidth; ++w)  for (int h = 0; h < characterHeight; ++h)   

Dans cette boucle, nous devons vérifier si un bloc particulier est solide. si oui, nous mettons inSolidTile à vrai, et quittez la boucle:

pour (var w = 0; w < characterWidth; ++w)  for (int h = 0; h < characterHeight; ++h)  if (mGrid[end.x + w, end.y + h] == 0 || mGrid[end.x + w, end.y + h] == 0)  inSolidTile = true; break;   if (inSolidTile) break; 

Mais ce n'est pas assez. Considérez la situation suivante:

Blocs verts: personnage; bloc bleu: but.

Si nous devions déplacer le personnage de sorte que son fond-la gauche bloc occupé le but, puis le fond-droite block serait bloqué dans un bloc solide, de sorte que l'algorithme penserait que, puisque le personnage ne correspond pas à la position de but, il est impossible d'atteindre le point final. Bien sûr, ce n'est pas vrai. on ne se soucie pas de savoir quelle partie du personnage atteint l'objectif. 

Pour résoudre ce problème, nous allons déplacer le point final vers la gauche, étape par étape, jusqu'au point où l'emplacement de l'objectif d'origine correspondrait au bas.-droite bloc de caractères:

pour (var i = 0; i < characterWidth; ++i)  inSolidTile = false; for (var w = 0; w < characterWidth; ++w)  for (var h = 0; h < characterHeight; ++h)  if (mGrid[end.x + w, end.y + h] == 0 || mGrid[end.x + w, end.y + h] == 0)  inSolidTile = true; break;   if (inSolidTile) break;  if (inSolidTile) end.x -= 1; else break; 

Notez que nous ne devrions pas simplement vérifier les coins en bas à gauche et à droite, car le cas suivant peut se produire:

Encore une fois, blocs verts: caractère; bloc bleu: but.

Ici, vous pouvez voir que si l’un des coins inférieurs occupe l’emplacement de l’objectif, le personnage sera toujours dans un sol solide de l’autre côté. Dans ce cas, nous devons faire correspondre le fond-centre bloquer avec le but.

Enfin, si nous ne trouvons pas d’endroit où le personnage pourrait s’intégrer, nous pourrions tout aussi bien quitter tôt l’algorithme:

if (inSolidTile == true) renvoie null;

Détermination de la position de départ

Pour voir si notre personnage est sur le terrain, nous devons vérifier si tout des cellules les plus basses du personnage sont directement au-dessus d'une tuile pleine.

Regardons le code que nous avons utilisé pour un caractère 1x1:

if (mMap.IsGround (start.x, start.y - 1)) firstNode.JumpLength = 0; else firstNode.JumpLength = (court) (maxCharacterJumpHeight * 2);

Nous déterminons si le point de départ est au sol en vérifiant si la tuile située immédiatement en dessous du point de départ est une tuile au sol. Pour mettre à jour le code, nous allons simplement le faire vérifier ci-dessous tous les blocs les plus bas du caractère.. 

D'abord, déclarons un booléen qui nous dira si le personnage commence sur le terrain. Au départ, nous supposons que ce n'est pas le cas:

bool startsOnGround = false;

Ensuite, nous allons parcourir tous les blocs de caractères les plus bas et vérifier si l'un d'entre eux se trouve directement au-dessus d'une tuile au sol. Si oui, alors nous avons mis commence au début à vrai et quittez la boucle:

pour (int x = start.x; x < start.x + characterWidth; ++x)  if (mMap.IsGround(x, start.y - 1))  startsOnGround = true; break;  

Enfin, nous définissons la valeur du saut selon que le personnage a commencé au sol ou non:

 if (startsOnGround) firstNode.JumpLength = 0; else firstNode.JumpLength = (court) (maxCharacterJumpHeight * 2);

Vérifier les limites du successeur

Nous devons également modifier la vérification des limites de notre successeur, mais ici nous n'avons pas besoin de vérifier toutes les tuiles. C'est assez bon pour vérifier la contour du personnage-les blocs autour du bord-parce que nous savons que la position du parent était bien.

Regardons comment nous avons vérifié les limites du successeur précédemment:

if (mGrid [mNewLocationX, mNewLocationY] == 0) continue; if (mMap.IsGround (mNewLocationX, mNewLocationY - 1)) onGround = true; else if (mGrid [mNewLocationX, mNewLocationY + characterHeight] == ​​0) atCeiling = true;

Nous mettrons à jour ceci en vérifiant si l'un des blocs de contour se trouve dans un bloc solide. Si tel est le cas, le personnage ne peut pas tenir dans la position et le successeur doit être ignoré..

Vérification des blocs supérieur et inférieur

Tout d'abord, parcourons tous les blocs les plus bas et les plus hauts du personnage, et vérifions s'ils recouvrent une tuile solide de notre grille:

pour (var w = 0; w < characterWidth; ++w)  if (mGrid[mNewLocationX + w, mNewLocationY] == 0 || mGrid[mNewLocationX + w, mNewLocationY + characterHeight - 1] == 0) goto CHILDREN_LOOP_END; 

le CHILDREN_LOOP_END label mène à la fin de la boucle suivante; en l'utilisant, nous évitons la nécessité de commencer Pause hors de la boucle, puis continuer au prochain successeur de la boucle du successeur. 

Quand une tuile dans l'air peut être considérée "sur le sol"

Si l'un des blocs du bas est juste au-dessus d'une tuile pleine, le successeur doit être au sol. Cela signifie que, même s'il n'y a pas de mosaïque solide directement sous la cellule de successeur elle-même, le successeur sera toujours considéré comme un Sur le sol noeud, si le caractère est assez large.

Le noeud rouge est un noeud "OnGround", même s'il n'est pas réellement sur le terrain.
pour (var w = 0; w < characterWidth; ++w)  if (mGrid[mNewLocationX + w, mNewLocationY] == 0 || mGrid[mNewLocationX + w, mNewLocationY + characterHeight - 1] == 0) goto CHILDREN_LOOP_END; if (mMap.IsGround(mNewLocationX + w, mNewLocationY - 1)) onGround = true; 

Vérifier si le personnage est au plafond

Si l'une des tuiles au-dessus du personnage est solide, alors le personnage est au plafond.

pour (var w = 0; w < characterWidth; ++w)  if (mGrid[mNewLocationX + w, mNewLocationY] == 0 || mGrid[mNewLocationX + w, mNewLocationY + characterHeight - 1] == 0) goto CHILDREN_LOOP_END; if (mMap.IsGround(mNewLocationX + w, mNewLocationY - 1)) onGround = true; if (mGrid[mNewLocationX + w, mNewLocationY + characterHeight] == 0) atCeiling = true; 

Vérification des blocs sur les côtés du personnage

Il ne reste plus qu’à vérifier qu’il n’ya pas de bloc plein dans les cellules gauche et droite du personnage. Si tel est le cas, nous pouvons ignorer le successeur, car notre personnage ne correspondra pas à cette position:

 pour (var h = 1; h < characterHeight - 1; ++h)  if (mGrid[mNewLocationX, mNewLocationY + h] == 0 || mGrid[mNewLocationX + characterWidth - 1, mNewLocationY + h] == 0) goto CHILDREN_LOOP_END; 

Conclusion

Nous avons supprimé une restriction assez importante de l'algorithme; maintenant, vous avez beaucoup plus de liberté en termes de taille des personnages de votre jeu.

Dans le prochain tutoriel de la série, nous utiliserons notre algorithme de recherche de chemin pour propulser un bot capable de suivre le chemin lui-même. il suffit de cliquer sur un emplacement et il va courir et sauter pour y arriver. Ceci est très utile pour les PNJ!