Puisque nous venons de terminer le contrôle des collisions au sol, nous pourrions aussi bien ajouter des plates-formes à sens unique pendant que nous y sommes. De toute façon, ils ne concerneront que le contrôle de collision au sol. Les plates-formes unidirectionnelles diffèrent des blocs solides en ce qu'elles n'arrêtent un objet que s'il tombe. De plus, nous allons également permettre à un personnage de descendre d'une telle plateforme..
Tout d'abord, lorsque nous voulons déposer une plate-forme à sens unique, nous voulons essentiellement ignorer la collision avec le sol. Une solution simple consiste à définir un décalage après lequel le personnage ou l’objet n’entre plus en collision avec une plate-forme..
Par exemple, si le personnage se trouve déjà deux pixels en dessous du sommet de la plate-forme, il ne devrait plus détecter de collision. Dans ce cas, lorsque nous voulons quitter la plate-forme, tout ce que nous avons à faire est de déplacer le personnage de deux pixels vers le bas. Créons cette constante de décalage.
public const float cOneWayPlatformThreshold = 2.0f;
Ajoutons maintenant une variable qui nous permettra de savoir si un objet est actuellement sur une plate-forme à sens unique.
public bool mOnOneWayPlatform = false;
Modifions la définition de la HasGround
fonction pour prendre également une référence à un booléen qui sera défini si l'objet a atterri sur une plate-forme à sens unique.
public bool HasGround (Vector2 oldPosition, position Vector2, vitesse Vector2, virgule flottante au sol, ref bool onOneWayPlatform)
Maintenant, après avoir vérifié si la tuile sur laquelle nous nous trouvons actuellement est un obstacle, et si ce n’est pas le cas, nous devrions vérifier s’il s’agit d’une plateforme à sens unique.
if (mMap.IsObstacle (tileIndexX, tileIndexY)) renvoie la valeur true; else if (mMap.IsOneWayPlatform (tileIndexX, tileIndexY)) onOneWayPlatform = true;
Comme expliqué précédemment, nous devons également nous assurer que cette collision est ignorée si nous sommes tombés au-delà de la cOneWayPlatformThreshold
sous la plate-forme.
Bien sûr, nous ne pouvons pas simplement comparer la différence entre le haut de la tuile et le capteur, car il est facile d’imaginer que même en cas de chute, nous pourrions passer bien en dessous de deux pixels du sommet de la plate-forme. Pour que les plates-formes à sens unique puissent arrêter un objet, nous voulons que la distance entre le haut de la mosaïque et le capteur soit inférieure ou égale à la cOneWayPlatformThreshold
plus le décalage de la position de cette image à la précédente.
if (mMap.IsObstacle (tileIndexX, tileIndexY)) renvoie la valeur true; else if (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true;
Enfin, il y a une dernière chose à considérer. Lorsque nous trouvons une plate-forme à sens unique, nous ne pouvons pas vraiment sortir de la boucle, car il y a des situations où le personnage est partiellement sur une plate-forme et partiellement sur un bloc solide.
Nous ne devrions pas vraiment considérer une telle position comme "sur une plate-forme à sens unique", car nous ne pouvons pas vraiment nous laisser tomber à partir de là, le bloc solide nous en empêche. C’est pourquoi nous devons d’abord continuer à rechercher un bloc solide, et s’il en trouve un avant de renvoyer le résultat, nous devons également définir onOneWayPlatform
à faux.
if (mMap.IsObstacle (tileIndexX, tileIndexY)) onOneWayPlatform = false; retourne vrai; else if (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true;
Maintenant, si nous avons examiné toutes les mosaïques que nous devions vérifier horizontalement et que nous avions trouvé une plate-forme à sens unique mais pas de bloc solide, alors nous pouvons être sûrs que nous sommes sur une plate-forme à sens unique à partir de laquelle nous pouvons descendre..
if (mMap.IsObstacle (tileIndexX, tileIndexY)) onOneWayPlatform = false; retourne vrai; else if (mMap.IsOneWayPlatform (tileIndexX, tileIndexY) && Mathf.Abs (checkedTile.y - groundY) <= Constants.cOneWayPlatformThreshold + mOldPosition.y - position.y) onOneWayPlatform = true; if (checkedTile.x >= bottomRight.x) if (onOneWayPlatform) renvoie true; Pause;
Ca y est, ajoutons maintenant à la classe de caractères une option pour descendre la plate-forme. Dans les états stand et run, nous devons ajouter le code suivant.
if (KeyState (KeyInput.GoDown)) if (mOnOneWayPlatform) mPosition.y - = Constants.cOneWayPlatformThreshold;
Voyons voir comment ça fonctionne.
Tout fonctionne correctement.
Nous devons créer une fonction analogue à HasGround pour chaque côté de l'AABB. Commençons par le plafond. Les différences sont les suivantes:
Voici la fonction modifiée.
public bool HasCeiling (Vector2 oldPosition, Vector2, hors flotteur de plafondY) var centre = position + MAABBOffset; var oldCenter = oldPosition + mAABBOffset; plafondY = 0,0f; var oldTopRight = oldCenter + mAABB.halfSize + Vector2.up - Vector2.right; var newTopRight = center + mAABB.halfSize + Vector2.up - Vector2.right; var newTopLeft = new Vector2 (newTopRight.x - mAABB.halfSize.x * 2.0f + 2.0f, newTopRight.y); int endY = mMap.GetMapTileYAtPoint (newTopRight.y); int begY = Mathf.Min (mMap.GetMapTileYAtPoint (oldTopRight.y) + 1, endY); int dist = Mathf.Max (Mathf.Abs (endY - begY), 1); int tileIndexX; pour (int tileIndexY = begY; tileIndexY <= endY; ++tileIndexY) var topRight = Vector2.Lerp(newTopRight, oldTopRight, (float)Mathf.Abs(endY - tileIndexY) / dist); var topLeft = new Vector2(topRight.x - mAABB.halfSize.x * 2.0f + 2.0f, topRight.y); for (var checkedTile = topLeft; ; checkedTile.x += Map.cTileSize) checkedTile.x = Mathf.Min(checkedTile.x, topRight.x); tileIndexX = mMap.GetMapTileXAtPoint(checkedTile.x); if (mMap.IsObstacle(tileIndexX, tileIndexY)) ceilingY = (float)tileIndexY * Map.cTileSize - Map.cTileSize / 2.0f + mMap.mPosition.y; return true; if (checkedTile.x >= topRight.x) break; return false;
De la même manière que nous avons géré le contrôle de collision pour le plafond et le sol, nous devons également vérifier si l'objet entre en collision avec le mur de gauche ou le mur de droite. Commençons par le mur de gauche. L'idée ici est à peu près la même, mais il y a quelques différences:
pour
la boucle doit parcourir les mosaïques verticalement, car le capteur est maintenant une ligne verticale.public bool CollidesWithLeftWall (Vector2 oldPosition, Vector2 position, hors float wallX) var centre = position + MAABBOffset; var oldCenter = oldPosition + mAABBOffset; wallX = 0,0f; var oldBottomLeft = oldCenter - mAABB.halfSize - Vector2.right; var newBottomLeft = center - mAABB.halfSize - Vector2.right; var newTopLeft = newBottomLeft + new Vector2 (0.0f, mAABB.halfSize.y * 2.0f); int tileIndexY; var endX = mMap.GetMapTileXAtPoint (newBottomLeft.x); var begX = Mathf.Max (mMap.GetMapTileXAtPoint (oldBottomLeft.x) - 1, endX); int dist = Mathf.Max (Mathf.Abs (endX - begX), 1); for (int tileIndexX = begX; tileIndexX> = endX; --tileIndexX) var bottomLeft = Vector2.Lerp (newBottomLeft, oldBottomLeft, (float) Mathf.Abs (endX - tileIndexX) / dist); var topLeft = bottomLeft + new Vector2 (0.0f, mAABB.halfSize.y * 2.0f); for (var checkedTile = bottomLeft;; vérifiéTile.y + = Map.cTileSize) vérifiéTile.y = Mathf.Min (vérifiéTile.y, topLeft.y); tileIndexY = mMap.GetMapTileYAtPoint (vérifiéTile.y); if (mMap.IsObstacle (tileIndexX, tileIndexY)) wallX = (float) tileIndexX * Map.cTileSize + Map.cTileSize / 2.0f + mMap.mPosition.x; retourne vrai; if (vérifiéTile.y> = topLeft.y) break; return false;
Enfin, créons le CollidesWithRightWall
fonction, qui, comme vous pouvez l’imaginer, fera la même chose que CollidesWithLeftWall
, mais au lieu d'utiliser un capteur sur la gauche, nous utiliserons un capteur sur le côté droit du personnage.
L’autre différence ici est qu’au lieu de vérifier les carreaux de droite à gauche, nous les vérifions de gauche à droite, car c’est la direction de déplacement supposée..
public bool CollidesWithRightWall (Vector2 oldPosition, Vector2 position, sortir du flotteur wallX) var center = position + mAABBOffset; var oldCenter = oldPosition + mAABBOffset; wallX = 0,0f; var oldBottomRight = oldCenter + nouveau Vector2 (mAABB.halfSize.x, -mAABB.halfSize.y) + Vector2.right; var newBottomRight = center + new Vector2 (mAABB.halfSize.x, -mAABB.halfSize.y) + Vector2.right; var newTopRight = newBottomRight + nouveau Vector2 (0.0f, mAABB.halfSize.y * 2.0f); var endX = mMap.GetMapTileXAtPoint (newBottomRight.x); var begX = Mathf.Min (mMap.GetMapTileXAtPoint (oldBottomRight.x) + 1, endX); int dist = Mathf.Max (Mathf.Abs (endX - begX), 1); int tileIndexY; pour (int tileIndexX = begX; tileIndexX <= endX; ++tileIndexX) var bottomRight = Vector2.Lerp(newBottomRight, oldBottomRight, (float)Mathf.Abs(endX - tileIndexX) / dist); var topRight = bottomRight + new Vector2(0.0f, mAABB.halfSize.y * 2.0f); for (var checkedTile = bottomRight; ; checkedTile.y += Map.cTileSize) checkedTile.y = Mathf.Min(checkedTile.y, topRight.y); tileIndexY = mMap.GetMapTileYAtPoint(checkedTile.y); if (mMap.IsObstacle(tileIndexX, tileIndexY)) wallX = (float)tileIndexX * Map.cTileSize - Map.cTileSize / 2.0f + mMap.mPosition.x; return true; if (checkedTile.y >= topRight.y) break; return false;
Toutes nos fonctions de détection de collision sont terminées. Utilisons-les donc pour compléter la réponse à la collision avec tilemap. Avant de faire cela, cependant, nous devons déterminer l'ordre dans lequel nous allons vérifier les collisions. Considérons les situations suivantes.
Dans ces deux situations, nous pouvons voir que le personnage a fini par se chevaucher avec une tuile, mais nous devons déterminer comment résoudre le chevauchement..
La situation sur la gauche est assez simple - nous pouvons voir que nous tombons tout droit, et à cause de cela nous devrions définitivement atterrir au sommet du bloc.
La situation à droite est un peu plus délicate, car en réalité nous pourrions atterrir sur le coin même de la tuile, et pousser le personnage au sommet est aussi raisonnable que le pousser à droite. Choisissons de donner la priorité au mouvement horizontal. Peu importe l’alignement que nous souhaitons faire en premier lieu; les deux choix semblent corrects en action.
Allons à notre UpdatePhysics
fonction et ajouter les variables qui contiendront les résultats de nos requêtes de collision.
float groundY = 0.0f, plafondY = 0.0f; float rightWallX = 0.0f, leftWallX = 0.0f;
Maintenant, commençons par regarder si nous devrions déplacer l'objet vers la droite. Les conditions ici sont les suivantes:
La dernière est une condition nécessaire, car si elle n'était pas remplie, nous aurions affaire à une situation similaire à celle de gauche dans l'image ci-dessus, dans laquelle nous ne devrions sûrement pas déplacer le personnage à droite..
si (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX) && mOldPosition.x - mAABB.halfSize.x + mAABBOffset.x >= leftWallX)
Si les conditions sont vraies, nous devons aligner le côté gauche de notre AABB sur le côté droit de la tuile, nous assurer que nous arrêtons de nous déplacer vers la gauche et marquer que nous sommes près du mur à gauche..
si (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX) && mOldPosition.x - mAABB.halfSize.x + mAABBOffset.x >= leftWallX) mPosition.x = leftWallX + mAABB.halfSize.x - mAABBOffset.x; mSpeed.x = Mathf.Max (mSpeed.x, 0.0f); mPushesLeftWall = true;
Si l’une des conditions autres que la dernière est fausse, nous devons définir mPushesLeftWall
à faux. En effet, la dernière condition étant fausse ne nous dit pas nécessairement que le personnage ne pousse pas le mur, mais inversement, elle nous dit qu’elle était déjà entrée en collision dans le cadre précédent. Pour cette raison, il est préférable de changer mPushesLeftWall
à false uniquement si l'une des deux premières conditions est également fausse.
si (mSpeed.x <= 0.0f && CollidesWithLeftWall(mOldPosition, mPosition, out leftWallX)) if (mOldPosition.x - mAABB.HalfSizeX + AABBOffsetX >= leftWallX) mPosition.x = leftWallX + mAABB.HalfSizeX - AABBOffsetX; mPushesLeftWall = true; mSpeed.x = Mathf.Max (mSpeed.x, 0.0f); else mPushesLeftWall = false;
Maintenant vérifions la collision avec le bon mur.
if (mSpeed.x> = 0.0f && CollidesWithRightWall (mOldPosition, mPosition, out rightWallX)) if (mOldPosition.x + mAABB.HalfSizeX + AABBOffsetX <= rightWallX) mPosition.x = rightWallX - mAABB.HalfSizeX - AABBOffsetX; mPushesRightWall = true; mSpeed.x = Mathf.Min(mSpeed.x, 0.0f); else mPushesRightWall = false;
Comme vous pouvez le constater, c’est la même formule que nous avons utilisée pour vérifier la collision avec le mur de gauche, mais en miroir..
Nous avons déjà le code pour vérifier la collision avec le sol, alors après cela, nous devons vérifier la collision avec le plafond. Rien de nouveau ici non plus. De plus, nous n'avons pas besoin de faire de vérifications supplémentaires, si ce n'est que la vitesse verticale doit être supérieure ou égale à zéro et que nous nous heurtons à une tuile qui se trouve sur nous..
if (mSpeed.y> = 0.0f && HasCeiling (mOldPosition, mPosition, out ceilingY)) mPosition.y = plafondY - MAABB.halfSize.y - MAABBOffset.y - 1.0f; mSpeed.y = 0.0f; mAtCeiling = true; else mAtCeiling = false;
Avant de vérifier si les réponses aux conflits fonctionnent, il y a encore une chose importante à faire: arrondir les valeurs des angles calculées pour les contrôles de collision. Nous devons faire cela pour que nos vérifications ne soient pas détruites par des erreurs en virgule flottante, qui pourraient provenir d'une position de carte étrange, d'une échelle de caractère ou simplement d'une taille étrange d'AABB..
Premièrement, pour notre facilité, créons une fonction qui transforme un vecteur de flotteurs en un vecteur de flotteurs arrondis.
Vector2 RoundVector (Vector2 v) retourne un nouveau vecteur2 (Mathf.Round (v.x), Mathf.Round (v.y));
Maintenant, utilisons cette fonction dans chaque contrôle de collision. D'abord, réparons le HasCeiling
une fonction.
var oldTopRight = RoundVector (oldCenter + mAABB.HalfSize + Vector2.up - Vector2.right); var newTopRight = RoundVector (centre + mAABB.HalfSize + Vector2.up - Vector2.right); var newTopLeft = RoundVector (nouveau Vector2 (newTopRight.x - mAABB.HalfSizeX * 2.0f + 2.0f, newTopRight.y));
Suivant est Sur le sol
.
var oldBottomLeft = RoundVector (oldCenter - mAABB.HalfSize - Vector2.up + Vector2.right); var newBottomLeft = RoundVector (centre - mAABB.HalfSize - Vector2.up + Vector2.right); var newBottomRight = RoundVector (nouveau Vector2 (newBottomLeft.x + mAABB.HalfSizeX * 2.0f - 2.0f, newBottomLeft.y));
PushesRightWall
.
var oldBottomRight = RoundVector (oldCenter + nouveau Vector2 (mAABB.HalfSizeX, -mAABB.HalfSizeY) + Vector2.right); var newBottomRight = RoundVector (center + nouveau Vector2 (mAABB.HalfSizeX, -mAABB.HalfSizeY) + Vector2.right); var newTopRight = RoundVector (newBottomRight + nouveau Vector2 (0.0f, mAABB.HalfSizeY * 2.0f));
et enfin, PushesLeftWall
.
var oldBottomLeft = RoundVector (oldCenter - mAABB.HalfSize - Vector2.right); var newBottomLeft = RoundVector (centre - mAABB.HalfSize - Vector2.right); var newTopLeft = RoundVector (newBottomLeft + nouveau Vector2 (0.0f, mAABB.HalfSizeY * 2.0f));
Cela devrait résoudre nos problèmes!
Ça va être ça. Testons comment nos collisions fonctionnent maintenant.
Voilà pour cette partie! Nous avons un ensemble de collisions de tilemap pleinement opérationnel, qui devrait être très fiable. Nous savons dans quelle position se trouve actuellement l’objet: que ce soit au sol, en touchant une tuile à gauche ou à droite, ou en heurtant un plafond. Nous avons également implémenté les plates-formes à sens unique, qui sont un outil très important dans tous les jeux de plateformes..
Dans la partie suivante, nous ajouterons des mécanismes permettant de saisir le rebord, ce qui augmentera encore le mouvement possible du personnage, alors restez à l'écoute.!