Problèmes de collision de la plateforme 2D AABB

51

http://dl.dropbox.com/u/3724424/Programming/Gifs/game6.gif

J'ai un problème avec la résolution de collision AABB.


Je résous l'intersection AABB en résolvant d'abord l'axe X, puis l'axe Y. Ceci est fait pour empêcher ce bug: http://i.stack.imgur.com/NLg4j.png


La méthode actuelle fonctionne bien lorsqu'un objet pénètre dans le lecteur et que celui-ci doit être poussé horizontalement. Comme vous pouvez le voir dans le fichier .gif, les pointes horizontales poussent correctement le lecteur.


Lorsque les pointes verticales pénètrent dans le lecteur, l'axe X est toujours résolu en premier. Cela rend "l'utilisation des pics comme un ascenseur" impossible.

Lorsque le joueur entre dans les pointes verticales (affecté par la gravité, il y tombe), il est poussé sur l'axe des Y car il n'y avait pas de chevauchement sur l'axe des X pour commencer.


Quelque chose que j’ai essayé est la méthode décrite dans la première réponse de ce lien: Détection de collision d’objets rectangulaires 2D

Cependant, les pointes et les objets en mouvement se déplacent en changeant leur position, et non leur vitesse, et je ne calcule pas leur prochaine position prédite tant que leur méthode Update () n'a pas été appelée. Inutile de dire que cette solution n'a pas fonctionné non plus. :(


Je dois résoudre le conflit AABB de manière à ce que les deux cas décrits ci-dessus fonctionnent comme prévu.

Ceci est mon code source de collision actuel: http://pastebin.com/MiCi3nA1

Je serais vraiment reconnaissant si quelqu'un pouvait se pencher là-dessus, car ce bogue était présent dans le moteur depuis le début, et je me suis battu pour trouver une bonne solution, sans succès. Cela me fait sérieusement passer des nuits à regarder le code de collision et à m'empêcher d'entrer dans la partie "amusante" et à coder la logique du jeu :(


J'ai essayé d'implémenter le même système de collision que dans la démo de la plate-forme XNA AppHub (en copiant-collant la plupart des éléments). Cependant, le bogue "sauter" se produit dans mon jeu, alors qu'il ne se produit pas dans la démo AppHub. [bug sautant: http://i.stack.imgur.com/NLg4j.png ]

Pour sauter, je vérifie si le joueur est "sur le sol", puis ajoute -5 à Velocity.Y.

Etant donné que Velocity.X du lecteur est supérieur à Velocity.Y (reportez-vous au quatrième panneau du diagramme), onGround est défini sur true alors que ce ne devrait pas être le cas, et permet donc au joueur de sauter en l'air.

Je crois que cela ne se produit pas dans la démo AppHub, car Velocity.X du joueur ne sera jamais plus élevé que Velocity.Y, mais je peux me tromper.

J'ai résolu cela auparavant en résolvant d'abord sur l'axe des X, puis sur l'axe des Y. Mais cela gâche la collision avec les pics comme je l'ai dit ci-dessus.

Vittorio Romeo
la source
5
En réalité, rien ne va mal. La méthode que j'utilise pousse d'abord sur l'axe des X, puis sur l'axe des Y. C'est pourquoi le joueur est poussé horizontalement, même sur les pointes verticales. Je dois trouver une solution qui évite le "problème de saut" et pousse le joueur sur l'axe peu profond (l'axe avec le moins de pénétration), mais je n'en trouve pas.
Vittorio Romeo
1
Vous pouvez détecter le visage de l'obstruction que le joueur touche et résoudre celui-ci
Ioachim
4
@ John Hobbs, lisez la question. Vee sait exactement ce que fait son code, mais ne sait pas comment résoudre un problème donné. Parcourir le code ne l'aidera pas dans cette situation.
AttaquerHobo
4
@Maik @Jonathan: Rien ne va "mal tourner" avec le programme - il comprend exactement où et pourquoi son algorithme ne fait pas ce qu'il veut. Il ne sait tout simplement pas comment changer l'algorithme pour faire ce qu'il veut. Le débogueur n'est donc pas utile dans ce cas.
BlueRaja - Danny Pflughoeft
1
@AttackingHobo @BlueRaja Je suis d'accord et j'ai supprimé mon commentaire. C'était moi en train de sauter aux conclusions sur ce qui se passait. Je m'excuse vraiment de vous avoir obligé à expliquer cela et d'avoir égaré au moins une personne. Honnêtement, vous pouvez compter sur moi pour bien assimiler la question avant que je ne laisse de réponse la prochaine fois.
doppelgreener

Réponses:

9

OK, j’ai compris pourquoi la démo de la plate-forme XNA AppHub n’a pas le bogue "jumping": la démo teste les mosaïques de collision de haut en bas . Lorsqu'il se trouve contre un "mur", le joueur peut chevaucher plusieurs tuiles. L'ordre de résolution est important car la résolution d'une collision peut également résoudre d'autres collisions (mais dans une direction différente). La onGroundpropriété n'est définie que lorsque la collision est résolue en poussant le lecteur vers le haut sur l'axe des ordonnées. Cette résolution ne se produira pas si les résolutions précédentes ont poussé le lecteur vers le bas et / ou horizontalement.

J'ai pu reproduire le bogue "jumping" dans la démo de XNA en modifiant cette ligne:

for (int y = topTile; y <= bottomTile; ++y)

pour ça:

for (int y = bottomTile; y >= topTile; --y)

(J'ai également modifié certaines des constantes liées à la physique, mais cela ne devrait pas avoir d'importance.)

Peut-être que le tri bodiesToChecksur l’axe des y avant de résoudre les collisions dans votre jeu corrigera le bogue "jumping". Je suggère de résoudre la collision sur l'axe de pénétration "peu profond", comme le suggère la démo XNA et Trevor . Notez également que le joueur de démonstration XNA est deux fois plus grand que les mosaïques compatibles entre elles, ce qui rend plus probable le cas de collision multiple.

Leftium
la source
Cela semble intéressant ... Pensez-vous que tout ordonner par la coordonnée Y avant de détecter les collisions pourrait résoudre tous mes problèmes? Y a-t-il un piège?
Vittorio Romeo
Aaaand j'ai trouvé le "catch". En utilisant cette méthode, le bogue de saut est corrigé, mais si je règle Velocity.Y à 0 après avoir atteint un plafond, il se reproduira.
Vittorio Romeo
@Vee: défini Position = nextPositionimmédiatement dans la forboucle, sinon les résolutions de collision non désirées (réglage onGround) se produisent toujours. Le joueur doit être poussé verticalement vers le bas (et jamais vers le haut) lorsqu’il atteint le plafond onGroundne doit donc jamais être réglé. Voici comment la démo XNA le fait, et je ne peux pas reproduire ici le bogue "ceiling".
Leftium
pastebin.com/TLrQPeBU - ceci est mon code: je travaille en fait sur la position elle-même, sans utiliser de variable "nextPosition". Cela devrait fonctionner comme dans la démo XNA, mais si je garde la ligne qui définit Velocity.Y sur 0 (ligne 57) sans commentaire, le bogue sautant se produit. Si je l'enlève, le joueur continue de flotter lorsqu'il saute dans un plafond. Cela peut-il être corrigé?
Vittorio Romeo
@Vee: votre code ne montre pas comment onGroundest défini, je ne peux donc pas rechercher pourquoi le saut est autorisé de manière incorrecte. La démo XNA met à jour sa isOnGroundpropriété analogue à l' intérieur HandleCollisions(). De plus, après avoir réglé Velocity.Ysur 0, pourquoi la gravité ne commence-t-elle pas à déplacer le lecteur vers le bas? Je suppose que la onGroundpropriété est mal définie. Jetez un coup d’œil à la mise à jour de la démo XNA previousBottomet de isOnGround( IsOnGround).
Leftium
9

La solution la plus simple pour vous consiste à vérifier les deux directions de collision par rapport à chaque objet du monde avant de résoudre toute collision, et à résoudre simplement la plus petite des deux "collisions composées" résultantes. Cela signifie que vous résolvez le moins possible, au lieu de toujours résoudre x en premier ou toujours en premier.

Votre code ressemblerait à quelque chose comme ça:

// This loop repeats, until our object has been fully pushed outside of all
// collision objects
while ( StillCollidingWithSomething(object) )
{
  float xDistanceToResolve = XDistanceToMoveToResolveCollisions( object );
  float yDistanceToResolve = YDistanceToMoveToResolveCollisions( object );
  bool xIsColliding = (xDistanceToResolve != 0.f);

  // if we aren't colliding on x (not possible for normal solid collision 
  // shapes, but can happen for unidirectional collision objects, such as 
  // platforms which can be jumped up through, but support the player from 
  // above), or if a correction along y would simply require a smaller move 
  // than one along x, then resolve our collision by moving along y.

  if ( !xIsColliding || fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) )
  {
    object->Move( 0.f, yDistanceToResolve );
  }
  else // otherwise, resolve the collision by moving along x
  {
    object->Move( xDistanceToResolve, 0.f );
  }
}

Grande révision : En lisant les commentaires d’autres réponses, je pense que j’ai enfin remarqué une hypothèse non précisée qui ferait que cette approche ne fonctionnerait pas (et qui explique pourquoi je ne comprenais pas les problèmes que certains, mais pas tous - les gens ont vu avec cette approche). Pour élaborer, voici un peu plus de pseudocode, montrant plus explicitement ce que les fonctions que j'ai déjà mentionnées sont supposées être en train de faire:

bool StillCollidingWithSomething( MovingObject object )
{
  // loop over every collision object in the world.  (Implementation detail:
  // don't test 'object' against itself!)
  for( int i = 0; i < collisionObjectCount; i++ )
  {
    // if the moving object overlaps any collision object in the world, then
    // it's colliding
    if ( Overlaps( collisionObject[i], object ) )
      return true;
  }
  return false;
}

float XDistanceToMoveToResolveCollisions( MovingObject object )
{
  // check how far we'd have to move left or right to stop colliding with anything
  // return whichever move is smaller
  float moveOutLeft = FindDistanceToEmptySpaceAlongNegativeX(object->GetPosition());
  float moveOutRight = FindDistanceToEmptySpaceAlongX(object->GetPosition());
  float bestMoveOut = min( fabs(moveOutLeft), fabs(moveOutRight) );

  return minimumMove;
}

float FindDistanceToEmptySpaceAlongX( Vector2D position )
{
  Vector2D cursor = position;
  bool colliding = true;
  // until we stop colliding...
  while ( colliding )
  {
    colliding = false;
    // loop over all collision objects...
    for( int i = 0; i < collisionObjectCount; i++ )
    {
      // and if we hit an object...
      if ( Overlaps( collisionObject[i], cursor ) )
      {
        // move outside of the object, and repeat.
        cursor.x = collisionObject[i].rightSide;
        colliding = true;

        // break back to the 'while' loop, to re-test collisions with
        // our new cursor position
        break;
      }
    }
  }
  // return how far we had to move, to reach empty space
  return cursor.x - position.x;  
}

Ce n'est pas un test "par objet"; cela ne fonctionne pas en testant et en résolvant l'objet en mouvement contre chaque tuile d'une carte du monde individuellement (cette approche ne fonctionnera jamais de manière fiable et échouera de façon de plus en plus catastrophique à mesure que la taille des tuiles diminue). Au lieu de cela, il teste l'objet en mouvement contre chaque objet de la carte du monde simultanément, puis sa résolution repose sur des collisions avec la carte du monde entier.

C’est ainsi que vous vous assurez, par exemple, que des carreaux muraux individuels dans un mur ne font jamais basculer le lecteur de haut en bas entre deux carreaux adjacents, le joueur étant ainsi coincé dans un espace non existant «entre» eux; les distances de résolution des collisions sont toujours calculées jusqu'à l'espace vide dans le monde, et pas simplement à la limite d'une seule tuile sur laquelle une autre tuile solide pourrait alors se trouver.

Trevor Powell
la source
1
i.stack.imgur.com/NLg4j.png - cela se produit si j'utilise la méthode ci-dessus.
Vittorio Romeo
1
Peut-être que je ne comprends pas bien votre code, mais laissez-moi expliquer le problème dans le diagramme: comme le joueur est plus rapide sur l'axe des X, la pénétration de X est supérieure à celle de Y. La collision choisit l'axe des Y (car la pénétration est plus petite) et pousse le joueur verticalement. Cela ne se produit pas si le lecteur est suffisamment lent pour que la pénétration en Y soit toujours supérieure à la pénétration en X.
Vittorio Romeo
1
@Vee À quel volet du diagramme faites-vous référence ici comme donnant un comportement incorrect en utilisant cette approche? Je suppose que le volet 5, dans lequel le joueur pénètre nettement moins en X qu'en Y, aurait pour conséquence d'être poussé le long de l'axe des X - ce qui est le comportement correct. Vous n'obtenez le mauvais comportement que si vous sélectionnez un axe pour une résolution basée sur la vélocité (comme dans votre image) et non sur la pénétration réelle (comme je l'ai suggéré).
Trevor Powell
1
@Trevor: Ah, je vois ce que vous dites. Dans ce cas, vous pourriez simplement le faireif(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja - Danny Pflughoeft
1
@ BlueRaja Grand point. J'ai modifié ma réponse pour utiliser moins de conditions, comme vous le suggérez. Merci!
Trevor Powell
6

Modifier

Je l'ai amélioré

Il semble que l’essentiel que j’ai changé est la classification des intersections.

Les intersections sont soit:

  • Bosses de plafond
  • Coups au sol (pousser hors du sol)
  • Collisions murales en plein air
  • Collisions murales mises à la terre

et je les résous dans cet ordre

Définir une collision au sol comme un joueur se trouvant à au moins 1/4 de la tuile

Donc, il s’agit d’une collision au sol et le joueur ( bleu ) va s’asseoir sur la tuile ( noir ) collision au sol

Mais ce n’est PAS une collision au sol et le joueur "glissera" du côté droit de la tuile quand il tombera dessus. ne va pas être un joueur de collision au sol va glisser par

Par cette méthode, le joueur ne sera plus pris sur les côtés des murs

bobobobo
la source
J'ai essayé votre méthode (voir mon implémentation: pastebin.com/HarnNnnp ) mais je ne sais pas où définir la vélocité du lecteur sur 0. J'ai essayé de la définir sur 0 si encrY <= 0 mais cela permet au joueur de s'arrêter en plein vol en glissant le long d'un mur et lui permet également à plusieurs reprises de sauter au mur.
Vittorio Romeo
Cependant, cela fonctionne correctement lorsque le lecteur interagit avec les pointes (comme indiqué dans le fichier .gif). J'apprécierais vraiment si vous pouviez m'aider à résoudre le problème du saut de mur (se coincer aussi dans les murs). Gardez à l'esprit que mes murs sont composés de plusieurs carreaux AABB.
Vittorio Romeo
Désolé de poster un autre commentaire, mais après avoir copié-collé votre code dans un tout nouveau projet, modifié sp en 5 et fait apparaître les tuiles en forme de mur, le bogue sautant se produit (reportez-vous à ce diagramme: i.stack.imgur.com /NLg4j.png)
Vittorio Romeo
Ok, essayez maintenant.
Bobobobo
+1 pour l'échantillon de code de travail (il manque cependant des blocs "spike" qui se déplacent horizontalement :)
Leftium
3

Remarque:
Mon moteur utilise une classe de détection de collision qui vérifie les collisions avec tous les objets en temps réel, uniquement lorsqu'un objet est déplacé et uniquement dans les carrés de la grille qu'il occupe actuellement.

Ma solution:

Lorsque je me suis heurté à des problèmes comme celui-ci dans ma plateforme 2d, j'ai fait quelque chose comme ce qui suit:

- (le moteur détecte rapidement les collisions possibles)
- (le jeu informe le moteur de vérifier les collisions exactes pour un objet donné) [renvoie le vecteur de l'objet *]
- (l'objet regarde la profondeur de pénétration ainsi que la position précédente par rapport à la position précédente de l'autre objet, pour déterminer de quel côté glisser
- ) (l'objet se déplace et fait la moyenne de sa vitesse avec la vitesse de l'objet (si l'objet se déplace))

ultifinitus
la source
"(L’objet examine la profondeur de pénétration, ainsi que la position antérieure par rapport à la position antérieure de l’autre objet, pour déterminer de quel côté glisser)" c’est la partie qui m’intéresse. Pouvez-vous préciser? Est-ce que cela réussit dans la situation montrée par le .gif et le diagramme?
Vittorio Romeo
Je n'ai pas encore trouvé de situation (autre que des vitesses élevées) dans laquelle cette méthode ne fonctionne pas.
Ultifinitus Le
Cela semble bien, mais pouvez-vous réellement expliquer comment résoudre les pénétrations? Résolvez-vous toujours sur le plus petit axe? Vérifiez-vous le côté de la collision?
Vittorio Romeo
Non, en fait, le petit axe n'est pas une bonne façon (principale) d'aller. A MON HUMBLE AVIS. Je résous généralement en fonction de quel côté était auparavant en dehors du côté de l'autre objet, c'est le plus commun. Lorsque cela échoue, je vérifie la vitesse et la profondeur.
Ultifinitus
2

Dans les jeux vidéo, l’approche consistait à avoir une fonction indiquant si votre position est valide, c’est-à-dire. bool ValidPos (int x, int y, int wi, int he);

La fonction vérifie la boîte englobante (x, y, wi, il) contre toute la géométrie du jeu et renvoie false s'il y a une intersection.

Si vous voulez bouger, dites à droite, prenez la position du joueur, ajoutez 4 à x et vérifiez si la position est valide, sinon, vérifiez avec + 3, + 2, etc. jusqu'à ce qu'elle le soit.

Si vous avez également besoin de la gravité, vous avez besoin d'une variable qui augmente tant que vous ne touchez pas le sol (frapper le sol: ValidPos (x, y + 1, wi, il) == vrai, y est positif vers le bas ici). si vous pouvez déplacer cette distance (c.-à-d. ValidPos (x, y + gravité, wi, il) retourne vrai), vous tombez, utile parfois lorsque vous ne devriez pas pouvoir contrôler votre personnage lors de la chute.

Maintenant, votre problème est que vous avez des objets dans votre monde qui bougent. Vous devez donc tout d’abord vérifier si votre ancienne position est toujours valide!

Si ce n'est pas le cas, vous devez trouver un poste qui l'est. Si les objets en jeu ne peuvent pas se déplacer plus rapidement que 2 pixels par tour de jeu, vous devez vérifier si la position (x, y-1) est valide, puis (x, y-2), puis (x + 1, y), etc. etc. tout l'espace entre (x-2, y-2) à (x + 2, y + 2) doit être vérifié. S'il n'y a pas de position valide, cela signifie que vous avez été «écrasé».

HTH

Valmond

Valmond
la source
J'avais l'habitude d'utiliser cette approche à l'époque de Game Maker. Je peux penser à des moyens de l’accélérer / de l’améliorer avec de meilleures recherches (disons une recherche binaire sur l’espace que vous avez parcouru, bien que cela puisse être plus lent en fonction de la distance parcourue et de la géométrie), mais le principal inconvénient de cette approche est: qu'au lieu de faire une seule vérification contre toutes les collisions possibles, vous faites quoi que ce soit, quel que soit votre changement de position.
Jeff
"vous faites quoi que ce soit que votre changement de position soit un contrôle" Désolé mais qu'est-ce que cela signifie exactement? BTW, bien sûr, vous utiliserez des techniques de partitionnement de l’espace (BSP, Octree, quadtree, Tilemap, etc.) pour accélérer le jeu, mais vous devrez toujours le faire de toute façon (si la carte est grande), la question n’est pas la même, mais à propos de l'algorithme utilisé pour déplacer (correctement) le joueur.
Valmond
@Valmond Je pense que @Jeff signifie que si votre joueur bouge de 10 pixels, vous pouvez avoir jusqu'à 10 détections de collision différentes.
Jonathan Connell
Si c'est ce qu'il veut dire, alors il a raison et il devrait en être ainsi (qui peut autrement dire si nous sommes arrêtés à +6 et non à +7?). Les ordinateurs sont rapides, je l'ai utilisé sur le S40 (~ 200mhz et un kvm pas si rapide) et cela a fonctionné comme un charme. Vous pouvez toujours essayer d’optimiser l’algo initial, mais cela vous donnera toujours des cas comme ceux que l’OP ont.
Valmond
C'est exactement ce que je voulais dire
Jeff
2

J'ai quelques questions avant de commencer à répondre à cette question. Premièrement, dans le bogue original dans lequel vous vous êtes coincé dans les murs, ces carreaux étaient-ils sur les carreaux individuels de gauche plutôt que sur un seul grand carreau? Et s'ils l'étaient, le joueur était-il coincé entre eux? Si vous avez répondu oui à ces deux questions, assurez-vous que votre nouveau poste est valide . Cela signifie que vous devrez vérifier s'il y a collision pour dire à votre joueur de bouger. Alors résolvez le déplacement minimum comme décrit ci-dessous, puis déplacez votre joueur sur cette base uniquement s’il peutdéplacez-vous là. Presque trop sous le nez: P Ceci introduira en fait un autre bug, que j’appellerai "corner cases". Essentiellement en termes de virages (comme en bas à gauche où les pics horizontaux apparaissent dans votre .gif, mais s’il n’y en avait pas) ne résoudraient pas une collision, car ils penseraient qu’aucune des résolutions que vous générez ne conduit à une position valide. . Pour résoudre ce problème, il vous suffit de conserver une liste indiquant si la collision a été résolue, ainsi qu'une liste de toutes les résolutions de pénétration minimales. Ensuite, si la collision n’a pas été résolue, passez en boucle sur toutes les résolutions que vous avez générées et gardez une trace des résolutions X maximale et Y maximale (les résolutions maximales ne doivent pas nécessairement provenir de la même résolution). Ensuite, résolvez la collision sur ces maximums. Cela semble résoudre tous vos problèmes ainsi que ceux que j'ai rencontrés.

    List<Vector2> collisions = new List<Vector2>();
        bool resolved = false;
        foreach (Platform p in testPlat)
        {
            Vector2 dif = p.resolveCollision(player.getCollisionMask());

            RectangleF newPos = player.getCollisionMask();

            newPos.X -= dif.X;
            newPos.Y -= dif.Y;


            if (!PlatformCollision(newPos)) //This checks if there's a collision (ie if we're entering an invalid space)
            {
                if (dif.X != 0)
                    player.velocity.X = 0; //Do whatever you want here, I like to stop my player on collisions
                if (dif.Y != 0)
                    player.velocity.Y = 0;

                player.MoveY(-dif.Y);
                player.MoveX(-dif.X);
                resolved = true;
            }
            collisions.Add(dif);
        }

        if (!resolved)
        {
            Vector2 max = Vector2.Zero;

            foreach (Vector2 v in collisions)
            {
                if (Math.Abs(v.X) > Math.Abs(max.X))
                {
                    max.X = v.X;
                }
                if (Math.Abs(v.Y) > Math.Abs(max.Y))
                {
                    max.Y = v.Y;
                }
            }

            player.MoveY(-max.Y);

            if (max.Y != 0)
                player.velocity.Y = 0;
            player.MoveX(-max.X);

            if (max.X != 0)
                player.velocity.X = 0;
        }

Une autre question, est-ce que les pointes que vous montrez sur une ou plusieurs tuiles? Si ce sont des carreaux minces individuels, vous devrez peut-être utiliser une approche différente pour les horizontales et les verticales que celle décrite ci-dessous. Mais si ce sont des carreaux entiers, cela devrait fonctionner.


Très bien, voici en gros ce que décrivait Trevor Powell. Puisque vous n'utilisez que des AABB, il vous suffit de trouver combien un rectangle pénètre dans l'autre. Cela vous donnera une quantité dans l'axe X et le Y. Choisissez le minimum parmi les deux et déplacez votre objet en collision le long de cet axe de cette quantité. C'est tout ce dont vous avez besoin pour résoudre une collision AABB. Vous n’avez JAMAIS besoin de vous déplacer le long de plus d’un axe dans une telle collision, vous ne devez donc jamais vous demander à quoi vous devez vous déplacer d’abord, car vous ne déplacerez que le minimum.

Logiciel Metanet a un tutoriel classique sur une approche ici . Cela prend aussi d'autres formes.

Voici une fonction XNA que j'ai créée pour trouver le vecteur de recouvrement de deux rectangles:

    public Point resolveCollision(Rectangle otherRect)
    {
        if (!isCollision(otherRect))
        {
            return Point.Zero;
        }

        int minOtherX = otherRect.X;
        int maxOtherX = otherRect.X + otherRect.Width;

        int minMyX = collisionMask.X;
        int maxMyX = collisionMask.X + collisionMask.Width;

        int minOtherY = otherRect.Y;
        int maxOtherY = otherRect.Y + otherRect.Height;

        int minMyY = collisionMask.Y;
        int maxMyY = collisionMask.Y + collisionMask.Height;

        int dx, dy;

        if (maxOtherX - minMyX < maxMyX - minOtherX)
        {
            dx = (maxOtherX - minMyX);
        }
        else
        {
            dx = -(maxMyX - minOtherX);
        }

        if (maxOtherY - minMyY < maxMyY - minOtherY)
        {
            dy = (maxOtherY - minMyY);
        }
        else
        {
            dy = -(maxMyY - minOtherY);
        }

        if (Math.Abs(dx) < Math.Abs(dy))
            return new Point(dx, 0);
        else
            return new Point(0, dy);
    }

(J'espère que c'est simple à suivre, car je suis sûr qu'il existe de meilleures façons de le mettre en œuvre ...)

isCollision (Rectangle) n’est littéralement qu’un appel à Rectangle.Intersects (Rectangle) de XNA.

J'ai testé cela avec des plates-formes mobiles et cela semble bien fonctionner. Je ferai d'autres tests plus similaires à votre .gif pour en être sûr et signaler si cela ne fonctionne pas.


Jeff
la source
Je suis un peu sceptique sur le commentaire que j'ai fait à ce sujet, qui ne fonctionne pas avec des plates-formes minces. Je ne peux pas penser à une raison pour laquelle ce ne serait pas. Aussi, si vous voulez la source, je peux télécharger un projet XNA pour vous
Jeff
Oui, je voulais juste en tester un peu plus et cela nous ramène au même problème de vous coincer dans le mur. C'est parce que là où les deux tuiles sont alignées, cela vous indique de monter à cause de la plus basse, puis de sortir (juste dans votre cas) pour la plus haute, annulant ainsi le mouvement de gravité si votre vitesse y est suffisamment lente. . Je vais devoir chercher une solution, il me semble avoir déjà eu ce problème auparavant.
Jeff
D'accord, j'ai trouvé un moyen extrêmement astucieux de contourner votre premier problème. La solution consiste à trouver le minimum de pénétration pour chaque AABB, en résolvant uniquement sur celui ayant le plus grand X, puis en une seconde passe uniquement sur le plus grand Y. Cela semble mauvais lorsque vous êtes écrasé, mais que les levées fonctionnent et que horizontalement, et votre problème de collage d'origine est parti. C'est un hackey, et je n'ai aucune idée de la façon dont cela se traduirait par d'autres formes. Une autre possibilité pourrait être de souder les géométries touchantes, mais je n'ai même pas tenté de le faire
Jeff
Les murs sont faits de carreaux 16x16. Les pointes sont une tuile 16x16. Si je résous sur l’axe minimum, le bogue sautant se produit (regardez le diagramme). Votre solution "extrêmement astucieuse" fonctionnerait-elle avec la situation montrée dans le fichier .gif?
Vittorio Romeo
Oui, ça marche dans le mien. Quelle est la taille de votre personnage?
Jeff
1

C'est simple.

Réécris les pointes. C'est la faute des pointes.

Vos collisions devraient se produire dans des unités discrètes et tangibles. Le problème pour autant que je puisse le voir est:

1. Player is outside spikes
2. Spikes move into intersection position with the player
3. Player resolves incorrectly

Le problème est avec l'étape 2, pas 3 !!

Si vous essayez de rendre les choses solides, vous ne devriez pas laisser les objets se glisser les uns dans les autres. Une fois que vous êtes dans une position d'intersection, si vous perdez votre place, le problème devient plus difficile à résoudre.

Les pointes devraient idéalement vérifier l’existence du joueur et, lorsqu’elles bougent, elles devraient le pousser au besoin.

Un moyen facile d'y parvenir est le joueur à avoir moveXet moveYfonctions qui comprennent le paysage et pousser le joueur par un certain delta ou dans la mesure où ils peuvent , sans frapper un obstacle .

Normalement, ils seront appelés par la boucle d'événement. Cependant, ils peuvent également être appelés par des objets afin de pousser le joueur.

function spikes going up:

    move spikes up

    if intersecting player:
        d = player bottom edge + spikes top edge

        player.moveY(-d)
    end if

end function

Il est évident que le joueur doit encore réagir aux pointes s'il va dans les .

La règle générale est qu'après avoir déplacé un objet, il doit se terminer dans une position sans intersection. De cette façon, il est impossible d’obtenir les défauts que vous voyez.

Chris Burt-Brown
la source
dans le cas extrême où moveYne parvient pas à supprimer l'intersection (car un autre mur est sur le chemin), vous pouvez à nouveau vérifier l'intersection et essayer moveX ou simplement le tuer.
Chris Burt-Brown