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.
la source
Réponses:
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
onGround
proprié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:
pour ça:
(J'ai également modifié certaines des constantes liées à la physique, mais cela ne devrait pas avoir d'importance.)
Peut-être que le tri
bodiesToCheck
sur 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.la source
Position = nextPosition
immédiatement dans lafor
boucle, sinon les résolutions de collision non désirées (réglageonGround
) se produisent toujours. Le joueur doit être poussé verticalement vers le bas (et jamais vers le haut) lorsqu’il atteint le plafondonGround
ne doit donc jamais être réglé. Voici comment la démo XNA le fait, et je ne peux pas reproduire ici le bogue "ceiling".onGround
est défini, je ne peux donc pas rechercher pourquoi le saut est autorisé de manière incorrecte. La démo XNA met à jour saisOnGround
propriété analogue à l' intérieurHandleCollisions()
. De plus, après avoir régléVelocity.Y
sur 0, pourquoi la gravité ne commence-t-elle pas à déplacer le lecteur vers le bas? Je suppose que laonGround
propriété est mal définie. Jetez un coup d’œil à la mise à jour de la démo XNApreviousBottom
et deisOnGround
(IsOnGround
).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:
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:
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.
la source
if(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
Modifier
Je l'ai amélioré
Il semble que l’essentiel que j’ai changé est la classification des intersections.
Les intersections sont soit:
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 )
Mais ce n’est PAS une collision au sol et le joueur "glissera" du côté droit de la tuile quand il tombera dessus.
Par cette méthode, le joueur ne sera plus pris sur les côtés des murs
la source
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))
la source
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
la source
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.
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:
(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.
la source
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:
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
moveX
etmoveY
fonctions 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.
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.
la source
moveY
ne 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.