Problèmes de détection de collision avec les AABB

8

J'ai implémenté une routine de détection de collision simple en utilisant des AABB entre mon sprite de jeu principal et diverses plates-formes (veuillez voir le code ci-dessous). Cela fonctionne très bien, mais j'introduis maintenant la gravité pour faire tomber mon personnage et cela a montré des problèmes avec ma routine de CD.

Je pense que le nœud du problème est que ma routine de CD fait reculer le sprite le long de l'axe dans lequel il a pénétré le moins dans l'autre sprite. Donc, s'il est plus dans l'axe Y que dans le X, il le fera reculer le long de l'axe X.

Cependant, maintenant mon sprite (héros) tombe maintenant à un taux de 30 pixels par image (c'est un écran de 1504 de haut - j'en ai besoin pour tomber aussi vite car je veux essayer de simuler la gravité `` normale '', tout plus lent a juste l'air bizarre) ) Je reçois ces problèmes. Je vais essayer de montrer ce qui se passe (et ce que je pense en être la cause - bien que je ne sois pas sûr) avec quelques images: (Code ci-dessous les images).

J'apprécierais quelques suggestions sur la façon de contourner ce problème.

entrez la description de l'image ici

Pour plus de précision, dans l'image ci-dessus à droite, quand je dis que la position est corrigée `` incorrectement '', c'est peut-être un peu trompeur. Le code lui-même fonctionne correctement pour la façon dont il est écrit, ou autrement dit, l'algorithme lui-même s'il se comporte comme je m'y attendrais, mais je dois changer son comportement pour éviter que ce problème ne se produise, j'espère que cela clarifie mes commentaires dans l'image .

entrez la description de l'image ici

Mon code

public boolean heroWithPlatforms(){

    //Set Hero center for this frame
    heroCenterX  = hero.xScreen+(hero.quadWidth/2);
    heroCenterY  = hero.yScreen+(hero.quadHeight/2);

    //Iterate through all platforms to check for collisions
    for(x=0;x<platformCoords.length;x+=2){

    //Set platform Center for this iteration
    platformCenterX = platformCoords[x]+(platforms.quadWidth/2);
    platformCenterY = platformCoords[x+1]+(platforms.quadHeight/2);

    // the Dif variables are the difference (absolute value)
    // of the center of the two sprites being compared (one for X axis difference
    //and on for the Y axis difference)
    difX = Math.abs(heroCenterX-platformCenterX);
    difY = Math.abs(heroCenterY-platformCenterY);

    //If 1
    //Check the difference between the 2 centers and compare it to the vector (the sum of
    //the two sprite's widths/2.  If it is smaller, then the sprites are pverlapping along
    //the X axis and we can now proceed to check the Y axis 
    if (difX<vecXPlatform){

    //If 2
    //Same as above but for the Y axis, if this is also true, then we have a collision
    if(difY<vecYPlatform){
        //we now know sprites are colliding, so we now need to know exactly by how much
        //penX will hold the value equal to the amount one sprite (hero, in this case)
        //has overlapped with the other sprite (the platform)
        penX = vecXPlatform-difX;
        penY = vecYPlatform-difY;
        //One sprite has penetrated into the other, mostly in the Y axis, so move sprite
        //back on the X Axis
        if (penX < penY){hero.xScreen-=penX*(heroCenterX-platformCenterX>=0 ? -1 : 1);}
        //Sprite has penetrated into the other, mostly in the X asis, so move sprite
        //back on the Y Axis
        else if (penY < penX) {hero.yScreen-=penY*(heroCenterY-platformCenterY>=0 ? -1 : 1);}
        return true;
        }//Close 'If' 2
        } //Close 'If' 1
    }
    //Otherwise, no collision

    return false;
    }
BungleBonce
la source
Pour élaborer, pourquoi déplacez-vous les sprites sur l'autre axe: //One sprite has penetrated into the other, mostly in the Y axis, so move sprite //back on the X Axis
Tom 'Blue' Piddock
Salut @Blue, parce que le sprite doit être corrigé le long de l'axe le moins pénétrant, donc si le sprite a pénétré la plate-forme plus dans l'axe X que le Y, c'est le Y qui doit être corrigé car il est le moindre des deux.
BungleBonce
doublon possible de Détection
Anko
J'ai lu cette question avant @Anko - elle ne me donne pas les réponses dont j'ai besoin. J'ai donc composé ma propre question (pas convaincu que le demandeur demande exactement la même chose, bien qu'il soit difficile d'être sûr) :-)
BungleBonce

Réponses:

1

lors de mes expériences avec HTML5 canvas et AABB, j'ai trouvé exactement ce que vous vivez. Cela s'est produit lorsque j'ai tenté de créer une plate-forme à partir de boîtes adjacentes de 32x32 pixels.

Solutions que j'ai essayées par ordre de mes préférences personnelles

1 - Diviser les mouvements par axe

Mon actuel, et je pense que je continuerai mon jeu avec. Mais assurez-vous de vérifier ce que j'appelle Phantom AABB si vous avez besoin d'une solution rapide sans avoir à modifier beaucoup votre conception actuelle.

Mon monde de jeu a une physique très simple. Il n'y a pas (encore) de concept de forces, seulement de déplacement. La gravité est un déplacement constant vers le bas. Pas d'accélération (pour l'instant).

Les déplacements sont appliqués par axe. Et en cela consiste cette première solution.

Chaque cadre de jeu:

player.moves.y += gravity;
player.moves.x += move_x;

move_x est défini en fonction du traitement d'entrée, si ce n'est pas la touche fléchée enfoncée, alors move_x = 0. Les valeurs inférieures à zéro signifient gauche, sinon à droite.

Traitez tous les autres sprites mobiles et décidez des valeurs de leur composant "moves", ils ont aussi le composant "moves".

Remarque: après la détection et la réponse aux collisions, le composant de déplacement doit être défini sur zéro, les propriétés x et y, car au prochain tick, la gravité sera à nouveau ajoutée. Si vous ne réinitialisez pas, vous vous terminerez avec un moves.y de deux fois la gravité souhaitée, et dans la coche suivante, ce sera trois fois.

Entrez ensuite le code d'application de déplacement et de détection de collision.

Le composant moves n'est pas vraiment la position actuelle du sprite. Le sprite n'a pas encore bougé même si moves.x ou y a changé. Le composant "moves" est un moyen de sauvegarder le déplacement à appliquer à la position du sprite dans la bonne partie de la boucle de jeu.

Traitez d'abord l'axe y (moves.y). Appliquez moves.y à sprite.position.y. Ensuite, appliquez la méthode AABB pour voir si l'image-objet en cours de traitement chevauche une plateforme AABB (vous avez déjà compris comment le faire). S'il se chevauche, déplacez le sprite dans l'axe y en appliquant la pénétration.y. Oui, contrairement à mon autre réponse, maintenant cette méthode de mouvement évolué ignore le penetration.x, pour cette étape du processus. Et nous utilisons le penetration.y même s'il est supérieur à penetration.x, nous ne le vérifions même pas.

Traitez ensuite l'axe x (moves.x). Appliquez moves.x à sprite.position.x. Et faites le contraire qu'auparavant. Cette fois, vous ne déplacerez le sprite que dans l'axe horizontal en appliquant penetration.x. Comme auparavant, peu importe si la pénétration.x est inférieure ou supérieure à la pénétration.y.

Le concept ici est que si le sprite ne bouge pas, et n'était initialement pas en collision, il restera comme ça dans l'image suivante (sprite.moves.x et y sont nuls). Le cas spécial où un autre objet de jeu se téléporte comme par magie à une position qui chevauche le début du sprite est quelque chose que je traiterai plus tard.

Quand un sprite commence à bouger. S'il ne se déplace que dans l'axe x, alors nous ne sommes intéressés que s'il pénètre quelque chose par la gauche ou la droite. Comme le sprite ne se déplace pas vers le bas, et nous le savons parce que la propriété y du composant moves est nulle, nous ne vérifions même pas la valeur calculée pour la pénétration.y, nous ne voyons que la pénétration.x. Si la pénétration existe dans l'axe de mouvement, nous appliquons la correction, sinon nous laissons simplement le sprite bouger.

Qu'en est-il du déplacement diagonal, pas nécessairement dans un angle de 45 degrés?

Le système proposé peut le gérer. Il vous suffit de traiter chaque axe séparément. Pendant votre simulation. Différentes forces sont appliquées au sprite. Même si votre moteur ne comprend pas encore les forces. Ces forces se traduisent par un déplacement arbitraire dans le plan 2D, ce déplacement est un vecteur 2D dans n'importe quelle direction et de n'importe quelle longueur. De ce vecteur, vous pouvez extraire l'axe y et l'axe x.

Que faire si le vecteur de déplacement est trop grand?

Vous ne voulez pas ça:

|
|
|
|
|
|
|
|_ _ _ _ _ _ _ _ _

Comme nos nouveaux mouvements appliquant la logique traitent d'abord un axe puis l'autre, un grand déplacement peut finir par être vu par le code de collision comme un grand L, cela ne détectera pas correctement les collisions avec des objets qui coupent votre vecteur de déplacement.

La solution consiste à diviser les déplacements importants en petites étapes, idéalement votre largeur ou hauteur de sprite, ou la moitié de votre largeur ou hauteur de sprite. Pour obtenir quelque chose comme ça:

|_
  |_
    |_
      |_
        |_
          |_
            |_
              |_

Qu'en est-il de la téléportation des sprites?

Si vous représentez la téléportation comme un grand déplacement, en utilisant le même composant de mouvements qu'un mouvement normal, alors pas de problème. Si vous ne le faites pas, alors vous devez penser à un moyen de marquer le sprite de téléportation à traiter par le code de collision (en ajoutant à une liste dédiée à cet effet?), Pendant le code de réponse, vous pouvez directement déplacer le sprite debout qui était dans la façon dont la téléportation a eu lieu.

2 - Phantom AABB

Avoir un AABB secondaire pour chaque sprite affecté par la gravité qui commence au bas du sprite, a la même largeur de l'AABB de sprite et une hauteur de 1 pixel.

L'idée ici est que ce fantôme AABB entre toujours en collision avec la plate-forme sous le sprite. Ce n'est que lorsque cet AABB fantôme ne chevauche pas que la gravité peut être appliquée au sprite.

Vous n'avez pas besoin de calculer le vecteur de pénétration pour l'AABB fantôme et la plate-forme. Vous n'êtes intéressé que par un simple test de collision, s'il chevauche une plate-forme, la gravité ne peut pas être appliquée. Votre sprite peut avoir une propriété booléenne qui indique s'il se trouve sur une plate-forme ou dans les airs. Vous êtes dans les airs lorsque votre AABB fantôme n'entre pas en collision avec une plate-forme en dessous du joueur.

Cette:

player.position.y += gravity;

Devient quelque chose comme ça:

if (player.onGround == false) player.position.y += gravity;

Très simple et fiable.

Vous laissez votre implémentation AABB telle quelle.

Le fantôme AABB peut avoir une boucle dédiée qui les traite, qui ne vérifie que les collisions simples et ne perd pas de temps à calculer le vecteur de pénétration. Cette boucle doit être antérieure au code de collision et de réponse normal. Vous pouvez appliquer la gravité pendant cette boucle, car les sprites dans l'air appliquent la gravité. Si l'AABB fantôme est en soi une classe ou une structure, il peut avoir une référence au sprite auquel il appartient.

Ceci est très similaire à l'autre solution proposée où vous vérifiez si votre joueur saute. Je donne un moyen possible de détecter si le joueur a les pieds sur une plate-forme.

Hatoru Hansou
la source
J'aime la solution Phantom AABB ici. J'ai déjà appliqué quelque chose de similaire pour contourner le problème. (Mais sans utiliser un AABB fantôme) - Je suis intéressé à en savoir plus sur votre méthode initiale ci-dessus, mais je suis un peu confus. Le composant Moves.x (et .y) est-il fondamentalement le montant par lequel le sprite sera déplacé (s'il est autorisé à se déplacer)? De plus, je ne sais pas comment cela pourrait aider directement ma situation. Serait reconnaissant si vous pouviez modifier votre réponse pour montrer un graphique en quoi cela pourrait aider avec ma situation (comme dans mon graphique ci-dessus) - merci!
BungleBonce
À propos du composant moves. Cela représente un déplacement futur, oui, mais vous ne vérifiez pas si vous autorisez le mouvement ou non, vous appliquez réellement le mouvement, par axe, d'abord y, puis x, puis voyez si vous êtes entré en collision avec quelque chose, si vous utilisez la pénétration vecteur pour reculer. Pourquoi enregistrer le déplacement pour un traitement ultérieur diffère de l'application effective du déplacement lorsqu'il y a une entrée utilisateur à traiter ou que quelque chose s'est produit dans le monde du jeu? J'essaierai de mettre cela en images lorsque j'éditerai ma réponse. Pendant ce temps, voyons si quelqu'un d'autre propose une alternative.
Hatoru Hansou
Ah OK - je pense que je comprends ce que vous dites maintenant. Donc, au lieu de déplacer x & y puis de vérifier la collision, avec votre méthode, vous déplacez d'abord X, puis vérifiez la collision, si elle entre en collision puis corrigez le long de l'axe X. Ensuite, déplacez Y et vérifiez la collision, s'il y a une collision, puis déplacez-vous le long de l'axe X? De cette façon, nous connaissons toujours le bon axe d'impact? Ai-je bien compris?! Merci!
BungleBonce
Avertissement: vous avez écrit "Ensuite, déplacez Y et vérifiez la collision, s'il y a une collision, puis déplacez-vous le long de l'axe X?" Son axe Y. Probablement une faute de frappe. Oui, c'est mon approche actuelle. Pour les déplacements très importants, plus que la largeur ou la hauteur de votre sprite, vous devez diviser en étapes plus petites, et c'est ce que je veux dire avec l'analogie L. Méfiez-vous de ne pas appliquer un grand déplacement sur l'axe des y puis un grand x, vous devez diviser à la fois par petites étapes et intercaler pour obtenir un comportement correct. De quoi se soucier uniquement si de grands déplacements sont autorisés dans votre jeu. Dans le mien, je sais qu'ils se produiront. Alors je me suis préparé.
Hatoru Hansou
Brillant @HatoruHansou, je n'ai jamais pensé à faire les choses de cette façon, je vais certainement essayer - merci beaucoup pour votre aide, je marquerai votre réponse comme acceptée. :-)
BungleBonce
5

Je combinerais les tuiles de plate-forme en une seule entité de plate-forme.

Par exemple, supposons que vous preniez les 3 tuiles des images et les combiniez en une seule entité, votre entité aurait une boîte de collision de:

Left   = box1.left
Right  = box3.right
Top    = box1.top
Bottom = box1.bottom

L'utiliser pour la collision vous donnera le y comme axe le moins pénétrant sur la majeure partie de la plateforme tout en vous permettant d'avoir des tuiles spécifiques dans la plateforme.

UnderscoreZero
la source
Salut @UnderscoreZero - merci, c'est une façon de procéder, la seule chose est que je stocke actuellement mes tuiles dans 3 tableaux séparés, un pour les tuiles du bord gauche, dessus pour les tuiles du milieu et un pour les tuiles du bord droit - je suppose que je pourrais combinez-les tous dans un seul tableau aux fins de CD, je ne sais pas comment je pourrais écrire du code pour le faire.
BungleBonce
Une autre option que vous pourriez essayer que de nombreux moteurs de physique utilisent est qu'une fois que l'objet a atterri sur un plan plat, ils désactivent la gravité de l'objet pour l'empêcher d'essayer de tomber. Une fois que l'objet reçoit une force suffisante pour l'éloigner de l'avion ou qu'il tombe du côté de l'avion, ils réactivent la gravité et effectuent la physique comme d'habitude.
UnderscoreZero
0

De tels problèmes sont courants avec les nouvelles méthodes de détection des collisions.

Je ne sais pas trop sur votre configuration de collision actuelle, mais voici comment cela devrait se passer:

Tout d'abord, faites ces variables:

  1. Faire une vitesse X et Y
  2. Faire une gravité
  3. Faites un saut
  4. Faire un saut

Ensuite, créez une minuterie pour calculer le delta afin d'affecter la vitesse de l'objet.

Troisièmement, procédez comme suit:

  1. Vérifiez si le joueur appuie sur le bouton de saut et ne saute pas, si c'est le cas, ajoutez jumpSpeed ​​à la vitesse Y.
  2. Soustrayez la gravité de la vitesse Y à chaque mise à jour pour simuler la gravité
  3. Si la hauteur y + du joueur est inférieure ou égale à la y de la plate-forme, définissez la vitesse Y sur 0 et placez le Y du joueur sur la y + la hauteur du joueur.

Si vous faites cela, il ne devrait y avoir aucun problème. J'espère que cela t'aides!

LiquidFeline
la source
Salut @ user1870398, merci, mais mon jeu n'a même pas de saut pour le moment, la question concernait les problèmes qui se posent lors de l'utilisation de la méthode de `` l'axe le moins pénétrant '' pour corriger la position du sprite. Le problème est que la gravité tire mon personnage plus loin dans mes plates-formes le long de l'axe Y qu'il ne se chevauche sur l'axe X, donc ma routine (comme c'est correct pour ce type d'algorithme) corrige l'axe X, donc je ne peux pas se déplacer le long de la plate-forme (comme la routine CD voit les tuiles individuelles et les traite comme telles, même si elles sont présentées comme une seule plate-forme).
BungleBonce
J'ai ton problème. C'est juste que vous ne corrigez pas correctement votre mouvement. Vous avez besoin d'une variable isJumping . Si vous appuyez sur le bouton de saut et que vous ne sautez pas, vous le définissez sur vrai, si vous touchez le sol, définissez-le sur faux, déplacez le Y du joueur à la bonne position ( platform.y + player.height ) et définissez isJumping sur false. Ensuite, juste après avoir vérifié ces choses, faites si (touche_gauche) player.velocity.x - = (player.isJumping? Player.speed: player.speed - player.groundFriction) . Je sais que vous n'avez pas encore implémenté le saut, il vous suffit donc toujours de définir isJumping sur false.
LiquidFeline
Je n'ai pas de saut mais si une variable 'ifJumping' est toujours définie sur false, en quoi est-ce différent de ne pas en avoir? (disons dans les jeux où il n'y a pas de saut) Je suis juste un peu confus quant à la façon dont un booléen «isjumping» pourrait aider dans ma situation - je vous serais reconnaissant de bien vouloir expliquer un peu plus - merci!
BungleBonce
Ce n'est pas nécessaire. Mieux vaut l'implémenter pendant que vous le programmez. Quoi qu'il en soit, la raison d' isJumping est d'organiser votre code et d'éviter les problèmes en ajoutant seulement de la gravité pendant que le joueur saute. Faites ce que j'ai recommandé. Cette méthode est ce que la plupart des gens utilisent. Même Minecraft l'utilise! C'est ainsi que la gravité est simulée efficacement! :)
LiquidFeline