Problèmes de saut de plate-forme avec les collisions AABB

9

Voir d'abord le schéma:

Lorsque mon moteur physique AABB résout une intersection, il le fait en trouvant l'axe où la pénétration est plus petite, puis en "poussant" l'entité sur cet axe.

Considérant l'exemple "sautant vers la gauche":

  • Si velocityX est plus grand que velocityY, AABB repousse l'entité sur l'axe Y, arrêtant efficacement le saut (résultat: le joueur s'arrête dans les airs).
  • Si velocityX est plus petit que velocitY (non illustré dans le diagramme), le programme fonctionne comme prévu, car AABB repousse l'entité sur l'axe X.

Comment puis-je résoudre ce problème?

Code source:

public void Update()
{
    Position += Velocity;
    Velocity += World.Gravity;

    List<SSSPBody> toCheck = World.SpatialHash.GetNearbyItems(this);

    for (int i = 0; i < toCheck.Count; i++)
    {
        SSSPBody body = toCheck[i];
        body.Test.Color = Color.White;

        if (body != this && body.Static)
        {                   
            float left = (body.CornerMin.X - CornerMax.X);
            float right = (body.CornerMax.X - CornerMin.X);
            float top = (body.CornerMin.Y - CornerMax.Y);
            float bottom = (body.CornerMax.Y - CornerMin.Y);

            if (SSSPUtils.AABBIsOverlapping(this, body))
            {
                body.Test.Color = Color.Yellow;

                Vector2 overlapVector = SSSPUtils.AABBGetOverlapVector(left, right, top, bottom);

                Position += overlapVector;
            }

            if (SSSPUtils.AABBIsCollidingTop(this, body))
            {                      
                if ((Position.X >= body.CornerMin.X && Position.X <= body.CornerMax.X) &&
                    (Position.Y + Height/2f == body.Position.Y - body.Height/2f))
                {
                    body.Test.Color = Color.Red;
                    Velocity = new Vector2(Velocity.X, 0);

                }
            }
        }               
    }
}

public static bool AABBIsOverlapping(SSSPBody mBody1, SSSPBody mBody2)
{
    if(mBody1.CornerMax.X <= mBody2.CornerMin.X || mBody1.CornerMin.X >= mBody2.CornerMax.X)
        return false;
    if (mBody1.CornerMax.Y <= mBody2.CornerMin.Y || mBody1.CornerMin.Y >= mBody2.CornerMax.Y)
        return false;

    return true;
}
public static bool AABBIsColliding(SSSPBody mBody1, SSSPBody mBody2)
{
    if (mBody1.CornerMax.X < mBody2.CornerMin.X || mBody1.CornerMin.X > mBody2.CornerMax.X)
        return false;
    if (mBody1.CornerMax.Y < mBody2.CornerMin.Y || mBody1.CornerMin.Y > mBody2.CornerMax.Y)
        return false;

    return true;
}
public static bool AABBIsCollidingTop(SSSPBody mBody1, SSSPBody mBody2)
{
    if (mBody1.CornerMax.X < mBody2.CornerMin.X || mBody1.CornerMin.X > mBody2.CornerMax.X)
        return false;
    if (mBody1.CornerMax.Y < mBody2.CornerMin.Y || mBody1.CornerMin.Y > mBody2.CornerMax.Y)
        return false;

    if(mBody1.CornerMax.Y == mBody2.CornerMin.Y)
        return true;

    return false;
}
public static Vector2 AABBGetOverlapVector(float mLeft, float mRight, float mTop, float mBottom)
{
    Vector2 result = new Vector2(0, 0);

    if ((mLeft > 0 || mRight < 0) || (mTop > 0 || mBottom < 0))
        return result;

    if (Math.Abs(mLeft) < mRight)
        result.X = mLeft;
    else
        result.X = mRight;

    if (Math.Abs(mTop) < mBottom)
        result.Y = mTop;
    else
        result.Y = mBottom;

    if (Math.Abs(result.X) < Math.Abs(result.Y))
        result.Y = 0;
    else
        result.X = 0;

    return result;
}
Vittorio Romeo
la source

Réponses:

2

Je viens de regarder le code que je n'ai pas essayé de prouver où il est faux.

J'ai regardé le code et ces 2 lignes me paraissaient étranges:

if ((Position.X >= body.CornerMin.X && Position.X <= body.CornerMax.X) &&
(Position.Y + Height/2f == body.Position.Y - body.Height/2f))

Vous vérifiez l'intervalle, puis vous vérifiez l'égalité? Je peux me tromper (il peut y avoir des arrondis en cours), mais il semble que cela puisse causer des problèmes.

user712092
la source
0

Il est difficile de lire le code d'autres personnes, mais je pense que c'est une solution de contournement possible (purement remue-méninges), bien que je ne puisse bien sûr pas le tester:

  1. Avant que la détection de collision ne se produise, enregistrez la vitesse du joueur dans une variable temporaire.
  2. Après avoir effectué votre réponse à la collision, vérifiez si la position X ou Y du joueur a été corrigée
  3. Si la position X a été modifiée, réinitialisez manuellement (comme une sorte de "réinitialisation de sécurité") la vitesse Y du joueur à celle qu'il avait avant la réponse.

Au fait, que se passe-t-il avec votre code actuel lorsque vos joueurs heurtent le toit en sautant?

TravisG
la source
Changer la vitesse ne résoudrait rien, car la vitesse n'est pas affectée par la réponse à la collision. La réponse change simplement la position du joueur, laissant la vitesse inchangée. Lorsque le joueur touche le toit, il flotte pendant un certain temps puis redescend. C'est prévu car je ne mets pas la vitesse à 0 quand elle atteint le plafond.
Vittorio Romeo
Que se passe-t-il si vous supprimez le code qui définit l'un des composants des vecteurs de résultat à zéro dans la méthode GetOverlapVector?
TravisG
L'entité est repoussée en diagonale, parfois elle ne fonctionne même pas correctement, d'autres fois elle s'enclenche comme si elle était sur une grille.
Vittorio Romeo
Je ne peux pas le comprendre pour le moment. La façon dont votre entité est expulsée devrait dépendre de sa distance du corps avec lequel elle entre en collision, mais votre algorithme le fait déjà. J'y reviendrai demain.
TravisG