A * Pathfinding pour les joueurs de plate-forme basés sur une grille 2D capture de rebords

Dans cette partie de notre série sur l’adaptation de l’algorithme A * pathfinding aux plateformes, nous introduirons une nouvelle mécanique dans le personnage: la capture de rebord. Nous apporterons également les modifications appropriées à la fois à l'algorithme de recherche de chemin et à l'IA du bot, afin qu'ils puissent utiliser la mobilité améliorée..

Démo

Vous pouvez jouer à la démonstration de Unity ou à la version WebGL (16 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, Clic du milieu placer une plate-forme à sens unique, et cliquer et faire glisser les curseurs pour changer leurs valeurs.

Mécanisme de saisie des rebords

Vue d'ensemble des contrôles

Examinons tout d’abord le fonctionnement de la mécanique de saisie du bord dans la démo pour comprendre comment nous devrions changer notre algorithme de recherche de trajectoire pour prendre en compte cette nouvelle mécanique..

Les commandes pour saisir le rebord sont assez simples: si le personnage est juste à côté d’un rebord en tombant, et que le joueur appuie sur la touche directionnelle gauche ou droite pour les déplacer vers ce rebord, il s’accroche lorsque le personnage est à la bonne position le rebord.

Une fois que le personnage s'empare d'un rebord, le joueur dispose de deux options: il peut sauter ou descendre. Sauter fonctionne normalement. le joueur appuie sur la touche de saut et la force du saut est identique à la force appliquée lors du saut depuis le sol. La descente se fait en appuyant sur le bouton bas (S), ou le keyn directionnel qui pointe du rebord.

Mise en œuvre des contrôles

Voyons comment les contrôles de saisie de la bordure fonctionnent dans le code. La première chose à faire est de détecter si le bord est à gauche ou à droite du personnage:

bool ledgeOnLeft = mLedgeTile.x * Map.cTileSize < mPosition.x; bool ledgeOnRight = !ledgeOnLeft;

Nous pouvons utiliser cette information pour déterminer si le personnage est supposé quitter le bord. Comme vous pouvez le constater, pour descendre, le joueur doit soit:

  • appuyez sur le bouton bas,
  • appuyez sur le bouton gauche lorsque nous attrapons un rebord à droite, ou
  • appuyez sur le bouton droit lorsque nous saisissons un rebord sur la gauche.
bool ledgeOnLeft = mLedgeTile.x * Map.cTileSize < mPosition.x; bool ledgeOnRight = !ledgeOnLeft; if (mInputs[(int)KeyInput.GoDown] || (mInputs[(int)KeyInput.GoLeft] && ledgeOnRight) || (mInputs[(int)KeyInput.GoRight] && ledgeOnLeft))  

Il y a une petite mise en garde ici. Prenons une situation où nous tenons le bouton bas et le bouton droit, lorsque le personnage tient un rebord à droite. Cela entraînera la situation suivante:

Le problème ici est que le personnage s'empare du rebord immédiatement après l'avoir lâché.. 

Une solution simple consiste à verrouiller le mouvement vers le rebord pendant quelques images après le retrait du rebord. C'est ce que l'extrait suivant fait:

bool ledgeOnLeft = mLedgeTile.x * Map.cTileSize < mPosition.x; bool ledgeOnRight = !ledgeOnLeft; if (mInputs[(int)KeyInput.GoDown] || (mInputs[(int)KeyInput.GoLeft] && ledgeOnRight) || (mInputs[(int)KeyInput.GoRight] && ledgeOnLeft))  if (ledgeOnLeft) mCannotGoLeftFrames = 3; else mCannotGoRightFrames = 3; 

Après cela, nous changeons l’état du personnage en Saut, qui gérera la physique du saut:

bool ledgeOnLeft = mLedgeTile.x * Map.cTileSize < mPosition.x; bool ledgeOnRight = !ledgeOnLeft; if (mInputs[(int)KeyInput.GoDown] || (mInputs[(int)KeyInput.GoLeft] && ledgeOnRight) || (mInputs[(int)KeyInput.GoRight] && ledgeOnLeft))  if (ledgeOnLeft) mCannotGoLeftFrames = 3; else mCannotGoRightFrames = 3; mCurrentState = CharacterState.Jump; 

Enfin, si le personnage n'est pas sorti du bord, nous vérifions si la touche de saut a été enfoncée. si tel est le cas, nous définissons la vitesse verticale du saut et changeons l'état:

bool ledgeOnLeft = mLedgeTile.x * Map.cTileSize < mPosition.x; bool ledgeOnRight = !ledgeOnLeft; if (mInputs[(int)KeyInput.GoDown] || (mInputs[(int)KeyInput.GoLeft] && ledgeOnRight) || (mInputs[(int)KeyInput.GoRight] && ledgeOnLeft))  if (ledgeOnLeft) mCannotGoLeftFrames = 3; else mCannotGoRightFrames = 3; mCurrentState = CharacterState.Jump;  else if (mInputs[(int)KeyInput.Jump])  mSpeed.y = mJumpSpeed; mCurrentState = CharacterState.Jump; 

Détection d'un point d'accrochage

Voyons comment déterminer si un rebord peut être saisi. Nous utilisons quelques points chauds autour du personnage:

Le contour jaune représente les limites du personnage. Les segments rouges représentent les capteurs muraux; ceux-ci sont utilisés pour gérer la physique des personnages. Les segments bleus représentent l'endroit où notre personnage peut se saisir d'un rebord.

Pour déterminer si le personnage peut saisir un rebord, notre code vérifie constamment le côté vers lequel il se dirige. Il cherche une tuile vide en haut du segment bleu, puis une tuile solide en dessous de celle-ci sur laquelle le personnage peut s'accrocher.. 

Remarque: la saisie de rebord est verrouillée si le personnage saute. Ceci peut être facilement remarqué dans la démo et dans l'animation de la section Vue d'ensemble des contrôles..

Le principal problème de cette méthode est que si notre personnage tombe à grande vitesse, il est facile de rater une fenêtre dans laquelle il peut saisir un rebord. Nous pouvons résoudre ce problème en recherchant toutes les mosaïques en partant de la position de l'image précédente jusqu'à celles en cours à la recherche de toute mosaïque vide au-dessus d'une solide. Si une telle tuile est trouvée, alors elle peut être saisie.

Maintenant que nous avons clarifié le fonctionnement de la mécanique de saisie du rebord, voyons comment l'intégrer à notre algorithme de recherche de chemin.

Changements Pathfinder

Rendre possible l'activation et la désactivation de la saisie des rebords

Tout d’abord, ajoutons un nouveau paramètre à notre FindPath fonction qui indique si le pathfinder doit envisager de saisir des rebords. Nous l'appellerons useLedges:

liste publique FindPath (début de Vector2i, fin de Vector2i, int characterWidth, int characterHeight, maxCharacterJumpHeight, bool useLedges)

Détecter les nœuds de saisie

Conditions

Nous devons maintenant modifier la fonction pour déterminer si un nœud particulier peut être utilisé pour la saisie de rebords. Nous pouvons le faire après avoir vérifié si le nœud est un nœud "à la terre" ou un nœud "au plafond", car dans les deux cas, il ne peut pas être utilisé pour la saisie de rebords..

if (onGround) newJumpLength = 0; else if (atCeiling) if (mNewLocationX! = mLocationX) newJumpLength = (court) Mathf.Max (maxCharacterJumpHeight * 2 + 1, jumpLength + 1); else newJumpLength = (court) Mathf.Max (maxCharacterJumpHeight * 2, jumpLength + 2);  else if (/ * vérifie s’il existe un noeud saisissant le rebord ici * /)  else if (mNewLocationY < mLocationY) 

Très bien: nous devons maintenant déterminer quand un nœud doit être considéré comme un nœud de saisie de rebord. Pour la clarté, voici un diagramme qui montre quelques exemples de positions de saisie de rebord:

… Et voici à quoi cela pourrait ressembler dans le jeu:

Les images-objets des personnages supérieurs sont étendues pour montrer à quoi cela ressemble avec des caractères de tailles différentes.

Les cellules rouges représentent les nœuds vérifiés; avec les cellules vertes, ils représentent le caractère de notre algorithme. Les deux situations du haut montrent des rebords de saisie de caractère 2x2 à gauche et à droite, respectivement. Les deux derniers affichent la même chose, mais la taille du personnage est 1x3 au lieu de 2x2.

Comme vous pouvez le constater, il devrait être assez facile de détecter ces cas dans l’algorithme. Les conditions pour le noeud grab de rebord seront les suivantes:

  1. Il y a une tuile pleine à côté de la tuile de personnage en haut à droite / en haut à gauche.
  2. Il y a une tuile vide au-dessus de la tuile solide trouvée.
  3. Il n'y a pas de tuile solide en dessous du personnage (pas besoin d'attraper des rebords si sur le sol).

Notez que la troisième condition est déjà prise en compte, car nous ne vérifions le nœud de saisie de rebord que si le personnage n'est pas au sol..

Tout d’abord, vérifions si nous voulons réellement détecter les saisies de rebords:

sinon si (useLedges)

Maintenant, vérifions s'il y a une tuile à droite du nœud de caractère en haut à droite:

else if (useLedges && mGrid [mNewLocationX + characterWidth, mNewLocationY + characterHeight - 1] == 0)

Et puis, si au-dessus de cette tuile, il y a un espace vide:

else if (useLedges && mGrid [mNewLocationX + characterWidth, mNewLocationY + characterHeight - 1] == 0 && mGrid [mNewLocationX + characterWidth, mNewLocationY + characterHeight]! = 0)

Maintenant, nous devons faire la même chose pour le côté gauche:

else if (useLedges && ((mGrid [mNewLocationX + characterWidth, mNewLocationY + characterHeight - 1] == 0 && mGrid [mNewLocationX + characterWidth, mNewLocationY + characterHeight - 1] = 1) == 0 && mGrid [mNewLocationX + characterWidth, mNewLocationY + characterHeight]! = 0) || 1] == 0 && mGrid [mNewLocationX - 1, mNewLocationY + characterHeight]! = 0)))

Il y a une autre chose que nous pouvons éventuellement faire, qui est de désactiver la recherche des nœuds de capture de rebord si la vitesse de chute est trop élevée, de sorte que le chemin ne renvoie pas certaines positions d'extraction de rebord extrêmes qu'il serait difficile de suivre par le bot:

else if (useLedges && jumpLength <= maxCharacterJumpHeight * 2 + 6 && ((mGrid[mNewLocationX + characterWidth, mNewLocationY + characterHeight - 1] == 0 && mGrid[mNewLocationX + characterWidth, mNewLocationY + characterHeight] != 0) || (mGrid[mNewLocationX - 1, mNewLocationY + characterHeight - 1] == 0 && mGrid[mNewLocationX - 1, mNewLocationY + characterHeight] != 0)))  

Après tout cela, nous pouvons être sûrs que le noeud trouvé est un noeud de rebord.

Ajout d'un nœud spécial

Que faisons-nous lorsque nous trouvons un nœud de saisie de rebord? Nous devons définir sa valeur de saut. 

Rappelez-vous que la valeur de saut est le nombre qui représente quelle phase du saut le personnage serait s'il atteignait cette cellule. Si vous avez besoin d'un récapitulatif sur le fonctionnement de l'algorithme, jetez un autre regard sur l'article théorique..

Il semble que tout ce que nous devions faire est de définir la valeur de saut du noeud sur 0, car à partir du point de saisie du rebord, le personnage peut effectivement réinitialiser un saut, comme s'il se trouvait au sol, mais il y a quelques points à prendre en compte ici. 

  • Tout d’abord, il serait bon que nous puissions déterminer d’un seul coup d’œil si le nœud est un nœud de capture de rebord ou non: cela sera extrêmement utile lors de la création d’un comportement de bot et lors du filtrage des nœuds.. 
  • Deuxièmement, le saut du sol peut généralement être exécuté à partir de l’endroit le plus approprié sur une tuile particulière, mais lorsqu’il saute d’une prise de rebord, le personnage est bloqué dans une position particulière et incapable de faire quoi que ce soit, mais commence à tomber ou à sauter vers le haut..

Compte tenu de ces avertissements, nous ajouterons une valeur de saut spéciale pour les nœuds de capture de bord. La valeur de cette valeur n'a pas vraiment d'importance, mais il est judicieux de la rendre négative, car cela réduira nos chances de mal interpréter le nœud..

const short cLedgeGrabJumpValue = -9;

Attribuons maintenant cette valeur lorsque nous détectons un nœud de saisie de rebord:

else if (useLedges && jumpLength <= maxCharacterJumpHeight * 2 + 6 && ((mGrid[mNewLocationX + characterWidth, mNewLocationY + characterHeight - 1] == 0 && mGrid[mNewLocationX + characterWidth, mNewLocationY + characterHeight] != 0) || (mGrid[mNewLocationX - 1, mNewLocationY + characterHeight - 1] == 0 && mGrid[mNewLocationX - 1, mNewLocationY + characterHeight] != 0)))  newJumpLength = cLedgeGrabJumpValue; 

Fabrication cLedgeGrabJumpValue négatif aura un effet sur le calcul du coût des noeuds; il fera en sorte que l'algorithme préfère utiliser des bords plutôt que de les ignorer. Il y a deux choses à noter ici:

  1. Les points d'appui en coin offrent une plus grande possibilité de mouvement que n'importe quel autre nœud en l'air, car le personnage peut sauter à nouveau en les utilisant; de ce point de vue, c’est une bonne chose que ces nœuds soient moins chers que d’autres. 
  2. Saisir trop de rebords conduit souvent à des mouvements non naturels, car les joueurs n’utilisent généralement pas les saisies de rebord, sauf s’ils sont nécessaires pour atteindre quelque part..

Dans l’animation ci-dessus, vous pouvez voir la différence entre monter lorsque les rebords sont préférés et quand ils ne le sont pas..

Pour l’instant, nous allons laisser le calcul des coûts en l'état, mais il est assez facile de le modifier pour rendre les nœuds de rebord plus chers..

Modifier la valeur de saut lors du saut ou de la chute d'un rebord

Maintenant, nous devons ajuster les valeurs de saut pour les nœuds qui partent du point de saisie du bord. Nous devons faire cela parce que sauter d'une position d'appui au rebord est très différent de sauter d'un sol. Il y a très peu de liberté pour sauter d'un rebord, car le personnage est fixé à un point particulier. 

Lorsqu'il est au sol, le personnage peut se déplacer librement à gauche ou à droite et sauter au moment le plus approprié.

Tout d'abord, définissons le cas lorsque le personnage passe d'une capture de rebord:

sinon si (mNewLocationY < mLocationY)  if (jumpLength == cLedgeGrabJumpValue) newJumpLength = (short)(maxCharacterJumpHeight * 2 + 4); else if (jumpLength % 2 == 0) newJumpLength = (short)Mathf.Max(maxCharacterJumpHeight * 2, jumpLength + 2); else newJumpLength = (short)Mathf.Max(maxCharacterJumpHeight * 2, jumpLength + 1); 

Comme vous pouvez le constater, la nouvelle longueur de saut est un peu plus grande si le personnage est tombé d'un bord: nous compensons ainsi le manque de maniabilité tout en saisissant un bord, ce qui se traduira par une vitesse verticale plus élevée avant que le joueur ne puisse atteindre d'autres nœuds..

Vient ensuite le cas où le personnage tombe d'un côté à partir de saisir un rebord:

else if (! onGround && mNewLocationX! = mLocationX) if (jumpLength == cLedgeGrabJumpValue) newJumpLength = (short) (maxCharacterJumpHeight * 2 + 3); sinon newJumpLength = (court) Mathf.Max (jumpLength + 1, 1); 

Tout ce que nous devons faire est de régler la valeur de saut sur la valeur en baisse.

Ignorer plus de nœuds

Nous devons ajouter quelques conditions supplémentaires pour le moment où nous devons ignorer les nœuds.. 

Tout d’abord, lorsque nous sautons d’une position de rebord, nous devons monter, pas sur le côté. Cela fonctionne de la même manière que de simplement sauter du sol. La vitesse verticale est beaucoup plus élevée que la vitesse horizontale possible à ce stade, et nous devons modéliser ce fait dans l'algorithme:

if (jumpLength == cLedgeGrabJumpValue && mLocationX! = mNewLocationX && newJumpLength < maxCharacterJumpHeight * 2) continue;

Si nous voulons autoriser le largage du rebord vers le côté opposé comme ceci:

Ensuite, nous devons éditer la condition qui ne permet pas le mouvement horizontal lorsque la valeur de saut est impair. C’est parce que, actuellement, notre valeur de saisie spéciale du rebord est égale à -9, il est donc approprié d'exclure tous les nombres négatifs de cette condition.

if (jumpLength> = 0 && jumpLength% 2! = 0 && mLocationX! = mNewLocationX) continue;

Mettre à jour le filtre de noeud

Enfin, passons au filtrage des nœuds. Tout ce que nous avons besoin de faire ici est d’ajouter une condition pour les noeuds saisissant les rebords afin que nous ne les filtrions pas. Nous devons simplement vérifier si la valeur de saut du nœud est égale à cLedgeGrabJumpValue:

|| (fNodeTmp.JumpLength == cLedgeGrabJumpValue)

L'ensemble du filtrage ressemble à ceci maintenant:

if ((mClose.Count == 0) || (mMap.IsOneWayPlatform (fNode.x, fNode.y - 1)) || (mGrid [fNode.x, fNode.y - 1] == 0 && mMap.IsOneWayWePlatform (fPrevNode.x, fPrevNode.y - 1)) || (fNodeTmp.JumpLength == 3) || (fNextNodeTmp.JumpLength! = 0 && fNodeTmp.JumpLength == 0) // Le saut de marque démarre || (fNodeTmp.JumpLength == 0 && fPrevNodeTmp.JumpLength! = 0) // marque des atterrissages || (fNode.y> mFermer [mFerme.Count - 1] .y && fNode.y> fNodeTmp.PY) || (fNodeTmp.JumpLength == cLedgeGrabJumpValue ) || (fNode.y < mClose[mClose.Count - 1].y && fNode.y < fNodeTmp.PY) || ((mMap.IsGround(fNode.x - 1, fNode.y) || mMap.IsGround(fNode.x + 1, fNode.y)) && fNode.y != mClose[mClose.Count - 1].y && fNode.x != mClose[mClose.Count - 1].x)) mClose.Add(fNode);

Voilà ce sont tous les changements que nous devions faire pour mettre à jour l'algorithme de recherche de chemin.

Changements de bot

Maintenant que notre chemin indique les endroits où un personnage peut saisir un rebord, modifions le comportement du bot pour qu'il utilise ces données..

Arrêter de recalculer atteintX et atteintY

Tout d’abord, pour clarifier les choses dans le bot, mettons à jour le GetContext () une fonction. Le problème actuel est que atteintX et atteint les valeurs sont constamment recalculées, ce qui supprime certaines informations sur le contexte. Ces valeurs sont utilisées pour voir si le bot a déjà atteint le nœud cible sur ses axes x et y, respectivement. (Si vous avez besoin d'un rappel sur la façon dont cela fonctionne, consultez mon tutoriel sur la programmation du bot.)

Modifions simplement ceci pour que si un caractère atteigne le nœud sur l'axe des x ou des y, ces valeurs restent vraies tant que nous ne passons pas au nœud suivant..

Pour rendre cela possible, nous devons déclarer atteintX et atteint en tant que membres de la classe:

public bool mReachedNodeX; public bool mReachedNodeY;

Cela signifie que nous n’avons plus besoin de les transmettre au GetContext () une fonction:

public void GetContext (sur Vector2 prevDest, sur Vector2 currentDest, sur Vector2 nextDest, sur bool destOnGround)

Avec ces modifications, nous devons également réinitialiser les variables manuellement chaque fois que nous commençons à nous déplacer vers le nœud suivant. La première occurrence se produit lorsque nous venons de trouver le chemin et que nous allons nous déplacer vers le premier noeud:

if (path! = null && path.Count> 1) for (var i = path.Count - 1; i> = 0; --i) mPath.Add (path [i]); mCurrentNodeId = 1; mReachedNodeX = false; mReachedNodeY = false;

La seconde est lorsque nous avons atteint le nœud cible actuel et que nous souhaitons passer au suivant:

if (mReachedNodeX && mReachedNodeY) int prevNodeId = mCurrentNodeId; mCurrentNodeId ++; mReachedNodeX = false; mReachedNodeY = false;

Pour arrêter de recalculer les variables, nous devons remplacer les lignes suivantes:

reachX = ReachedNodeOnXAxis (pathPosition, prevDest, currentDest); reachY = ReachedNodeOnYAxis (pathPosition, prevDest, currentDest);

… Avec ceux-ci, qui détecteront si nous avons atteint un nœud sur un axe uniquement si nous ne l'avons pas déjà atteint:

if (! mReachedNodeX) mReachedNodeX = ReachedNodeOnXAxis (cheminPosition, prevDest, currentDest); if (! mReachedNodeY) mReachedNodeY = ReachedNodeOnYAxis (pathPosition, prevDest, currentDest);

Bien sûr, nous devons également remplacer chaque autre occurrence de atteintX et atteint avec les versions nouvellement déclarées mReachedNodeX et mReachedNodeY.

Voir si le personnage a besoin de saisir un rebord

Déclarons quelques variables que nous utiliserons pour déterminer si le bot doit saisir un rebord et, le cas échéant, lequel:

public bool mGrabsLedges = false; bool mMustGrabLeftLedge; bool mMustGrabRightLedge;

mGrabsLedges est un drapeau que nous transmettons à l’algorithme pour lui faire savoir s’il doit trouver un chemin incluant les saisies de rebord. mMustGrabLeftLedge et mMustGrabRightLedge sera utilisé pour déterminer si le prochain nœud est un rebord, et si le bot doit saisir le rebord à gauche ou à droite.

Ce que nous voulons faire maintenant, c’est créer une fonction qui, à partir d’un nœud, sera capable de détecter si le personnage de ce nœud sera capable de saisir un rebord.. 

Nous aurons besoin de deux fonctions pour cela: l’un vérifiera si le personnage peut saisir un rebord à gauche et l’autre vérifiera si le personnage peut saisir un rebord à droite. Ces fonctions fonctionneront de la même manière que notre code de recherche de chemins pour détecter les rebords:

public bool CanGrabLedgeOnLeft (int nodeId) return (mMap.IsObstacle (mPath [n °Iod]] .x - 1, mPath [nubId] .y + mHeight - 1) &&! mMap.IsObstacle (mPath [nubId] .x, mPath [nodeId] .y + mHeight));  public bool CanGrabLedgeOnRight (int nodeId) return (mMap.IsObstacle (mPath [nodeId] .x + mWidth, mPath [nodeId] .y + mHeight - 1) &&! mMap.IsObstacle (mPath [nodeId] .x mPath [nodeId] .y + mHeight)); 

Comme vous pouvez le constater, nous vérifions s'il y a une tuile solide à côté de notre personnage avec une tuile vide au-dessus.

Passons maintenant au GetContext () fonction et attribuer les valeurs appropriées à mMustGrabRightLedge et mMustGrabLeftLedge. Nous devons les configurer pour vrai si le personnage est supposé saisir des rebords (c'est-à-dire, si mGrabsLedges est vrai) et s’il ya un rebord à saisir.

mMustGrabLeftLedge = mGrabsLedges &&! destOnGround && CanGrabLedgeOnLeft (mCurrentNodeId); mMustGrabRightLedge = mGrabsLedges &&! destOnGround && CanGrabLedgeOnRight (mCurrentNodeId);

Notez également que nous ne voulons pas attraper de rebords si le nœud de destination est au sol.

Mettre à jour les valeurs de saut

Comme vous le remarquerez peut-être, la position du personnage lors de la saisie d'un bord est légèrement différente de celle du personnage situé juste en dessous:

La position de saisie du rebord est un peu plus élevée que la position debout, même si ces personnages occupent le même nœud. Cela signifie que pour attraper un rebord, il faudra un saut légèrement plus haut que de sauter sur une plate-forme, et nous devons en tenir compte..

Regardons la fonction qui détermine combien de temps le bouton de saut doit être enfoncé:

public int GetJumpFramesForNode (int prevNodeId) int currentNodeId = prevNodeId + 1; if (mPath [currentNodeId] .y - mPath [prevNodeId] .y> 0 && mOnGround) int jumpHeight = 1; pour (int i = currentNodeId; i < mPath.Count; ++i)  if (mPath[i].y - mPath[prevNodeId].y >= jumpHeight) jumpHeight = mPath [i] .y - mPath [prevNodeId] .y; if (mPath [i] .y - mPath [prevNodeId] .y < jumpHeight || mMap.IsGround(mPath[i].x, mPath[i].y - 1)) return GetJumpFrameCount(jumpHeight);   return 0; 

Tout d'abord, nous allons changer la condition initiale. Le bot devrait être capable de sauter, pas seulement du sol, mais aussi lorsqu'il s'agrippe à un rebord:

if (mPath [currentNodeId] .y - mPath [prevNodeId] .y> 0 && (mOnGround || mCurrentState == CharacterState.GrabLedge))

Nous devons maintenant ajouter quelques images supplémentaires si le saut est nécessaire pour saisir un rebord. Tout d'abord, nous devons savoir s'il peut réellement le faire. Nous allons donc créer une fonction qui nous dira si le personnage peut saisir un bord à gauche ou à droite:

bool public CanGrabLedge (int nodeId) return CanGrabLedgeOnLeft (nodeId) || CanGrabLedgeOnRight (nodeId); 

Ajoutons maintenant quelques images au saut lorsque le bot doit saisir un rebord:

if (mPath [i] .y - mPath [prevNodeId] .y> = jumpHeight) jumpHeight = mPath [i] .y - mPath [prevNodeId] .y; if (mPath [i] .y - mPath [prevNodeId] .y < jumpHeight || mMap.IsGround(mPath[i].x, mPath[i].y - 1)) return (GetJumpFrameCount(jumpHeight)); else if (grabLedges && CanGrabLedge(i)) return (GetJumpFrameCount(jumpHeight) + 4);

Comme vous pouvez le constater, nous prolongons le saut de 4 cadres, ce qui devrait bien faire le travail dans notre cas.

Mais il y a encore une chose que nous devons changer ici, qui n'a pas vraiment grand chose à voir avec l'accaparement des marges. Il corrige un cas où le nœud suivant a la même hauteur que le nœud actuel, mais n'est pas au sol, et le nœud suivant est plus haut, ce qui signifie qu'un saut est nécessaire:

if ((mPath [currentNodeId] .y - mPath [prevNodeId] .y> 0 || | mPath [currentNodeId] .y - mPath [prevNodeId] .y == 0 &&! mMap.IsGround (mPath [currentNodeId] .x, x mPath [currentNodeId] .y - 1) && mPath [currentNodeId + 1] .y - mPath [prevNodeId] .y> 0)) && (mOnGround || mCurrentState == CharacterState.GrabLedge))

Implémenter la logique de mouvement pour saisir et déposer les rebords

Nous voudrons scinder la logique de saisie du rebord en deux phases: une pour le bot qui n’est pas encore assez proche du rebord pour commencer à saisir, donc nous voulons simplement continuer à bouger comme d’habitude et une pour le départ en toute sécurité du garçon. se dirigeant vers elle pour l'attraper.

Commençons par déclarer un booléen qui indiquera si nous sommes déjà passés à la deuxième phase. Nous l'appellerons mCanGrabLedge:

public bool mGrabsLedges = false; bool mMustGrabLeftLedge; bool mMustGrabRightLedge; bool mCanGrabLedge = false; 

Nous devons maintenant définir des conditions qui permettront au personnage de passer à la deuxième phase. Ce sont assez simples:

  • Le bot a déjà atteint le nœud de l'objectif sur l'axe X.
  • Le bot doit saisir le rebord gauche ou droit.
  • Si le bot se dirige vers le rebord, il heurtera un mur au lieu d'aller plus loin..

Très bien, les deux premières conditions sont très simples à vérifier maintenant car nous avons déjà effectué tout le travail nécessaire:

if (! mCanGrabLedge && mReachedNodeX && (mMustGrabLeftLedge || mMustGrabRightLedge))  else if (mReachedNodeX && mReachedNodeY)

Maintenant, la troisième condition, nous pouvons séparer en deux parties. Le premier s'occupera de la situation où le personnage se déplacera du bas vers le rebord et le second du haut. Les conditions que nous voulons définir pour le premier cas sont les suivantes:

  • La position actuelle du bot est inférieure à la position cible (elle approche du bas).
  • Le haut du cadre de sélection du personnage est supérieur à la hauteur de la tuile du rebord.
(pathPosition.y < currentDest.y && (currentDest.y + Map.cTileSize*mHeight) < pathPosition.y + mAABB.HalfSizeY * 2)

Si le bot approche du haut, les conditions sont les suivantes:

  • La position actuelle du bot est supérieure à la position cible (elle approche du haut).
  • La différence entre la position du personnage et la position cible est inférieure à la hauteur du personnage.
(pathPosition.y> currentDest.y && pathPosition.y - currentDest.y < mHeight * Map.cTileSize)

Maintenant, combinons tout cela et positionnons le drapeau qui indique que nous pouvons nous déplacer en toute sécurité vers un rebord:

 else if (! mCanGrabLedge && mReachedNodeX && (mMustGrabLeftLedge || mMustGrabRightLedge) && ((pathPosition.y < currentDest.y && (currentDest.y + Map.cTileSize*mHeight) < pathPosition.y + mAABB.HalfSizeY * 2) || (pathPosition.y > currentDest.y && pathPosition.y - currentDest.y < mHeight * Map.cTileSize)))  mCanGrabLedge = true; 

Il y a encore une chose que nous voulons faire ici, c'est de commencer immédiatement à nous diriger vers le rebord:

if (! mCanGrabLedge && mReachedNodeX && (mMustGrabLeftLedge || mMustGrabRightLedge) && ((pathPosition.y < currentDest.y && (currentDest.y + Map.cTileSize*mHeight) < pathPosition.y + mAABB.HalfSizeY * 2) || (pathPosition.y > currentDest.y && pathPosition.y - currentDest.y < mHeight * Map.cTileSize)))  mCanGrabLedge = true; if (mMustGrabLeftLedge) mInputs[(int)KeyInput.GoLeft] = true; else if (mMustGrabRightLedge) mInputs[(int)KeyInput.GoRight] = true; 

OK, maintenant avant cette énorme condition, créons une plus petite. Ce sera essentiellement une version simplifiée pour le mouvement lorsque le bot est sur le point de saisir un rebord:

if (mCanGrabLedge && mCurrentState! = CharacterState.GrabLedge) if (mMustGrabLeftLedge) mInputs [(int) KeyInput.GoLeft] = true; else if (mMustGrabRightLedge) mInputs [(int) KeyInput.GoRight] = true;  else if (! mCanGrabLedge && mReachedNodeX && (mMustGrabLeftLedge || mMustGrabRightLedge) &&

C'est la logique principale derrière l'accaparement, mais il reste encore quelques choses à faire.. 

Nous devons éditer la condition dans laquelle nous vérifions s'il est correct de passer au nœud suivant. Actuellement, la condition ressemble à ceci:

else if (mReachedNodeX && mReachedNodeY)

Maintenant, nous devons également passer au nœud suivant si le bot était prêt à saisir le bord et l'a effectivement fait:

else if (((mReachedNodeX && mReachedNodeY) || (mCanGrabLedge && mCurrentState == CharacterState.GrabLedge)))

Sauter et tomber du rebord

Une fois que le bot est sur le rebord, il devrait être capable de sauter normalement, ajoutons donc une condition supplémentaire à la routine de saut:

if (mFramesOfJumping> 0 && (mCurrentState == CharacterState.GrabLedge ||! mOnGround || (mReachedNodeX &&! destOnGround) || (mOnGround).) (intInputs ((int) KeyInput.Jump) if (! mOnGround) --mFramesOfJumping; 

La prochaine chose que le bot doit être capable de faire est de laisser tomber gracieusement le rebord. Avec la mise en œuvre actuelle, c'est très simple: si nous saisissons un rebord et que nous ne sautons pas, il est évident que nous devons y échapper.!

if (mCurrentState == Character.CharacterState.GrabLedge && mFramesOfJumping <= 0)  mInputs[(int)KeyInput.GoDown] = true; 

C'est tout! Maintenant, le personnage peut très facilement quitter la position de saisie du bord, peu importe s'il a besoin de sauter ou tout simplement de descendre.

Arrêtez de saisir les corniches tout le temps!

Pour le moment, le bot saisit tous les rebords qu'il peut, qu'il soit logique de le faire ou non.. 

Une solution à ce problème consiste à attribuer un coût heuristique élevé aux saisies de rebord, de sorte que l'algorithme donne