Problemi di collisione 2D Platformer AABB


51

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

Ho un problema con la risoluzione delle collisioni AABB.


Risolvo l'intersezione AABB risolvendo prima l'asse X, quindi l'asse Y. Questo viene fatto per prevenire questo errore: http://i.stack.imgur.com/NLg4j.png


Il metodo corrente funziona bene quando un oggetto si sposta nel giocatore e il giocatore deve essere spinto in orizzontale. Come puoi vedere in .gif, i picchi orizzontali spingono correttamente il giocatore.


Quando i picchi verticali si spostano nel giocatore, tuttavia, l'asse X viene ancora risolto per primo. Questo rende impossibile "usare le punte come un ascensore".

Quando il giocatore si sposta nei picchi verticali (influenzati dalla gravità, cade in essi), viene spinto sull'asse Y, perché all'inizio non c'era sovrapposizione sull'asse X.


Qualcosa che ho provato è stato il metodo descritto nella prima risposta di questo collegamento: rilevamento di collisioni di oggetti rettangolari 2D

Tuttavia, i picchi e gli oggetti in movimento si muovono cambiando la loro posizione, non la velocità, e non calcolo la loro prossima posizione prevista fino a quando non viene chiamato il loro metodo Update (). Inutile dire che questa soluzione non ha funzionato neanche. :(


Devo risolvere la collisione AABB in modo che entrambi i casi sopra descritti funzionino come previsto.

Questo è il mio attuale codice sorgente di collisione: http://pastebin.com/MiCi3nA1

Le sarei davvero grato se qualcuno potesse esaminarlo, dal momento che questo bug è stato presente nel motore fin dall'inizio, e ho avuto difficoltà a trovare una buona soluzione, senza successo. Questo mi sta seriamente facendo passare le notti a guardare il codice di collisione e mi impedisce di arrivare alla "parte divertente" e codificare la logica di gioco :(


Ho provato a implementare lo stesso sistema di collisione della demo del platform XNA AppHub (copiando e incollando la maggior parte delle cose). Tuttavia, il bug "jumping" si verifica nel mio gioco, mentre non si verifica nella demo di AppHub. [bug saltellante: http://i.stack.imgur.com/NLg4j.png ]

Per saltare, controllo se il giocatore è "onGround", quindi aggiungo -5 a Velocity.Y.

Dato che Velocity.X del giocatore è più alto di Velocity.Y (fare riferimento al quarto pannello nel diagramma), onGround è impostato su true quando non dovrebbe essere, e quindi consente al giocatore di saltare a mezz'aria.

Credo che ciò non accada nella demo di AppHub perché Velocity.X del giocatore non sarà mai superiore a Velocity.Y, ma potrei sbagliarmi.

L'ho risolto prima risolvendo prima sull'asse X, quindi sull'asse Y. Ma questo accorcia la collisione con le punte come ho detto sopra.


5
In realtà non c'è niente che non va. Il metodo che sto usando spinge prima sull'asse X, poi sull'asse Y. Ecco perché il giocatore viene spinto orizzontalmente anche sui picchi verticali. Devo trovare una soluzione che eviti il ​​"problema del salto" e spinga il giocatore sull'asse superficiale (l'asse con meno penetrazione), ma non riesco a trovarne.
Vittorio Romeo,

1
È possibile rilevare quale faccia dell'ostruzione tocca il giocatore e risolverla
Ioachim

4
@Johnathan Hobbs, leggi la domanda. Vee sa esattamente cosa sta facendo il suo codice, ma non sa come risolvere un certo problema. Il passaggio attraverso il codice non lo aiuterà in questa situazione.
Attaccando

4
@Maik @Jonathan: Nulla sta "andando storto" con il programma: capisce esattamente dove e perché il suo algoritmo non fa quello che vuole. Semplicemente non sa come modificare l'algoritmo per fare ciò che vuole. Quindi il debugger non è utile in questo caso.
BlueRaja - Danny Pflughoeft il

1
@AttackingHobo @BlueRaja Sono d'accordo e ho eliminato il mio commento. Sono stato io a saltare alle conclusioni su quello che stava succedendo. Mi scuso davvero per averti costretto a spiegarlo e per aver sbagliato a guidare almeno una persona. Onestamente, puoi contare su di me per assorbire davvero correttamente la domanda prima di lasciare qualsiasi risposta la prossima volta.
doppelgreener,

Risposte:


9

OK, ho capito perché la demo del platform XNA AppHub non ha il bug "jumping": la demo verifica le tessere di collisione dall'alto verso il basso . Quando si trova contro un "muro", il giocatore può sovrapporsi a più tessere. L'ordine di risoluzione è importante perché la risoluzione di una collisione può anche risolvere altre collisioni (ma in una direzione diversa). La onGroundproprietà viene impostata solo quando la collisione viene risolta spingendo il giocatore verso l'alto sull'asse y. Questa risoluzione non si verifica se le risoluzioni precedenti hanno spinto il giocatore verso il basso e / o in orizzontale.

Sono stato in grado di riprodurre il bug "jumping" nella demo di XNA modificando questa riga:

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

a questo:

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

(Ho anche modificato alcune delle costanti legate alla fisica, ma questo non dovrebbe importare.)

Forse l'ordinamento bodiesToChecksull'asse y prima di risolvere le collisioni nel tuo gioco risolverà il bug "saltare". Suggerisco di risolvere la collisione sull'asse "superficiale" di penetrazione, come fa la demo di XNA e Trevor suggerisce . Si noti inoltre che il lettore demo XNA è due volte più alto delle tessere in grado di scontrarsi, rendendo più probabile il caso di collisione multipla.


Sembra interessante ... Pensi che ordinare tutto dalla coordinata Y prima di rilevare le collisioni potrebbe risolvere tutti i miei problemi? C'è qualche cattura?
Vittorio Romeo,

Aaaand ho trovato la "cattura". Usando questo metodo, l'errore di salto viene corretto, ma se imposto Velocity.Y su 0 dopo aver colpito un soffitto, si verifica nuovamente.
Vittorio Romeo,

@Vee: impostato Position = nextPositionimmediatamente all'interno del forloop, altrimenti onGroundsi verificano ancora le risoluzioni (impostazione ) indesiderate . Il giocatore dovrebbe essere spinto verticalmente verso il basso (e mai in alto) quando si colpisce il soffitto, quindi onGroundnon dovrebbe mai essere impostato. Questo è il modo in cui la demo di XNA lo fa, e non posso riproporre il bug "soffitto" lì.
Leftium,

pastebin.com/TLrQPeBU - questo è il mio codice: in realtà lavoro sulla posizione stessa, senza usare una variabile "nextPosition". Dovrebbe funzionare come nella demo XNA, ma se mantengo la riga che imposta Velocity.Y su 0 (riga 57) senza commenti, si verifica il bug di salto. Se lo rimuovo, il giocatore continua a galleggiare quando salta su un soffitto. Questo può essere risolto?
Vittorio Romeo,

@Vee: il tuo codice non mostra come onGroundè impostato, quindi non riesco a capire perché il salto sia errato. La demo di XNA aggiorna la sua isOnGroundproprietà analoga all'interno HandleCollisions(). Inoltre, dopo aver impostato Velocity.Ysu 0, perché la gravità non inizia a spostare di nuovo il lettore verso il basso? Suppongo che la onGroundproprietà non sia impostata correttamente. Dai un'occhiata a come la demo di XNA si aggiorna previousBottome isOnGround( IsOnGround).
Leftium,

9

La soluzione più semplice per te sarà quella di controllare entrambe le direzioni di collisione contro ogni oggetto nel mondo prima di risolvere eventuali collisioni, e semplicemente risolvere la più piccola delle due "collisioni composte" risultanti. Ciò significa che si risolve con la minima quantità possibile, invece di risolvere sempre prima x o sempre risolvere prima y.

Il tuo codice sarebbe simile a questo:

// 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 revisione : dalla lettura dei commenti ad altre risposte, penso di aver finalmente notato un'ipotesi non dichiarata, che impedirà a questo approccio di funzionare (e che spiega perché non riuscivo a capire i problemi che alcuni - ma non tutti - la gente ha visto con questo approccio). Per elaborare, ecco qualche altro pseudocodice, che mostra in modo più esplicito ciò che le funzioni a cui ho fatto riferimento in precedenza dovrebbero effettivamente fare:

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;  
}

Questo non è un test "per coppia di oggetti"; non funziona testando e risolvendo l'oggetto in movimento su ciascuna piastrella di una mappa del mondo individualmente (tale approccio non funzionerà mai in modo affidabile e fallisce in modi sempre più catastrofici quando le dimensioni delle tessere diminuiscono). Invece, sta testando l'oggetto in movimento contro ogni oggetto nella mappa del mondo contemporaneamente, e quindi risolto in base a collisioni contro l'intera mappa del mondo.

Questo è il modo in cui ti assicuri che (per esempio) le singole tessere di muro all'interno di un muro non facciano mai rimbalzare il giocatore su e giù tra due tessere adiacenti, facendo sì che il giocatore rimanga intrappolato in uno spazio inesistente 'tra' loro; le distanze di risoluzione delle collisioni vengono sempre calcolate fino allo spazio vuoto nel mondo, non semplicemente al limite di una singola tessera che potrebbe quindi avere un'altra tessera solida su di essa.


1
i.stack.imgur.com/NLg4j.png - questo succede se uso il metodo sopra.
Vittorio Romeo,

1
Forse non sto sottolineando correttamente il tuo codice, ma lasciami spiegare il problema nel diagramma: poiché il giocatore è più veloce sull'asse X, la penetrazione X è> rispetto alla penetrazione Y. La collisione sceglie l'asse Y (perché la penetrazione è più piccola) e spinge il giocatore in verticale. Ciò non si verifica se il lettore è abbastanza lento in modo che la penetrazione Y sia sempre> rispetto alla penetrazione X.
Vittorio Romeo,

1
@Vee A quale riquadro del diagramma ti riferisci, qui, come dare un comportamento errato usando questo approccio? Sto assumendo il riquadro 5, in cui il giocatore sta chiaramente penetrando meno in X che in Y, il che comporterebbe la sua espulsione lungo l'asse X, che è il comportamento corretto. Si ottiene il cattivo comportamento solo se si seleziona un asse per la risoluzione basato sulla velocità (come nell'immagine) e non sulla penetrazione effettiva (come ho suggerito).
Trevor Powell,

1
@Trevor: Ah, capisco cosa stai dicendo. In tal caso, potresti semplicemente farcelaif(fabs( yDistanceToResolve ) < fabs( xDistanceToResolve ) || xDistanceToResolve == 0) { object->Move( 0.f, yDistanceToResolve ); } else { object->Move( xDistanceToResolve, 0.f ); }
BlueRaja - Danny Pflughoeft,

1
@BlueRaja Ottimo punto. Ho modificato la mia risposta per usare meno condizionali, come suggerisci tu. Grazie!
Trevor Powell,

6

modificare

L'ho migliorato

Sembra che la cosa chiave che ho cambiato sia classificare le intersezioni.

Le intersezioni sono:

  • Dossi del soffitto
  • Colpi al suolo (spingi fuori dal pavimento)
  • Collisioni a mezz'aria della parete
  • Collisioni a muro a terra

e li risolvo in quell'ordine

Definisci una collisione a terra quando il giocatore si trova ad almeno 1/4 di percorso sulla tessera

Quindi questa è una collisione a terra e il giocatore ( blu ) siederà in cima alla tessera ( nero ) collisione al suolo

Ma questa NON è una collisione a terra e il giocatore "scivolerà" sul lato destro della tessera quando ci atterra non vuole essere un giocatore di collisione a terra che passa

Con questo metodo il giocatore non verrà più catturato ai lati delle pareti


Ho provato il tuo metodo (vedi la mia implementazione: pastebin.com/HarnNnnp ) ma non so dove impostare la velocità del giocatore su 0. Ho provato a impostarlo su 0 se encrY <= 0 ma questo fa fermare il giocatore a mezz'aria mentre scivola lungo un muro e gli permette anche di saltare ripetutamente il muro.
Vittorio Romeo,

Tuttavia, questo funziona correttamente quando il giocatore interagisce con i picchi (come mostrato in .gif). Gradirei davvero se potessi aiutarmi a risolvere il problema del salto a muro (anche rimanere bloccato nei muri). Tieni presente che le mie pareti sono fatte di diverse piastrelle AABB.
Vittorio Romeo,

Ci scusiamo per aver pubblicato un altro commento, ma dopo aver incollato il codice in un nuovo progetto, cambiando sp a 5 e facendo apparire le tessere a forma di muro, si verifica il bug di salto (fare riferimento a questo diagramma: i.stack.imgur.com /NLg4j.png)
Vittorio Romeo,

Ok, provalo adesso.
Bobobobo,

+1 per l'esempio di codice funzionante (mancano i blocchi "spike" in movimento orizzontale, però :)
Leftium

3

Forenote: il
mio motore utilizza una classe di rilevamento delle collisioni che controlla le collisioni con tutti gli oggetti in tempo reale, solo quando un oggetto si muove e solo nei quadrati della griglia che occupa attualmente.

La mia soluzione:

Quando mi sono imbattuto in problemi come questo nel mio platform 2d, ho fatto qualcosa di simile al seguente:

- (il motore rileva rapidamente possibili collisioni)
- (il gioco informa il motore di verificare le esatte collisioni per un particolare oggetto) [restituisce il vettore dell'oggetto *]
- (l'oggetto osserva la profondità di penetrazione, nonché la posizione precedente rispetto alla posizione precedente di un altro oggetto, per determinare da quale lato scivolare fuori)
- (l'oggetto si muove e media la sua velocità con la velocità dell'oggetto (se l'oggetto si sta muovendo))


"(l'oggetto guarda la profondità di penetrazione, così come la posizione precedente rispetto alla posizione precedente di un altro oggetto, per determinare da quale parte scivolare fuori)" questa è la parte che mi interessa. Puoi elaborare? Riesce nella situazione mostrata dal .gif e dal diagramma?
Vittorio Romeo,

Devo ancora trovare una situazione (diversa dalle alte velocità) in cui questo metodo non funziona.
ultifinitus

Sembra buono, ma puoi davvero spiegare come risolvi le penetrazioni? Risolvi sempre sull'asse più piccolo? Controlli il lato della collisione?
Vittorio Romeo,

No, di fatto l'asse più piccolo non è una buona strada (primaria) da percorrere. A PARER MIO. Di solito risolvo in base a quale lato era precedentemente al di fuori dell'altro oggetto, è il più comune. Quando fallisce, allora controllo in base a velocità e profondità.
ultifinitus,

2

Nei videogiochi che ho programmato l'approccio era quello di avere una funzione che dicesse se la tua posizione è valida, cioè. bool ValidPos (int x, int y, int wi, int he);

La funzione controlla il rettangolo di selezione (x, y, wi, he) contro tutta la geometria del gioco e restituisce false in caso di intersezione.

Se vuoi muoverti, di 'a destra, prendi la posizione del giocatore, aggiungi da 4 a x e controlla se la posizione è valida, altrimenti controlla con + 3, + 2 ecc.

Se hai anche bisogno di gravità, hai bisogno di una variabile che cresce fintanto che non colpisci il suolo (colpire il terreno: ValidPos (x, y + 1, wi, he) == vero, y è positivo verso il basso qui). se riesci a muovere quella distanza (es. ValidPos (x, y + gravità, wi, lui) ritorna vero) stai cadendo, utile a volte quando non dovresti essere in grado di controllare il tuo personaggio quando cadi.

Ora, il tuo problema è che hai oggetti nel tuo mondo che si muovono, quindi prima di tutto devi controllare se la tua vecchia posizione è ancora valida!

In caso contrario, è necessario trovare una posizione che lo sia. Se gli oggetti in gioco non possono muoversi più velocemente di 2 pixel per giro di gioco, dovresti verificare se la posizione (x, y-1) è valida, quindi (x, y-2) quindi (x + 1, y) ecc. ecc. l'intero spazio tra (x-2, y-2) e (x + 2, y + 2) deve essere controllato. Se non ci sono posizioni valide, significa che sei stato "schiacciato".

HTH

Valmond


Ho usato questo approccio nei miei giorni di Game Maker. Posso pensare a modi per accelerarlo / migliorarlo con ricerche migliori (ad esempio una ricerca binaria sullo spazio che hai percorso, anche se a seconda della distanza percorsa e della geometria, forse è più lenta), ma il principale svantaggio di questo approccio è che invece di dire un controllo contro tutte le possibili collisioni, stai facendo qualunque cosa il tuo cambio di posizione sia un controllo.
Jeff,

"stai facendo tutto ciò che il tuo cambio di posizione è assegni" Mi dispiace ma cosa significa esattamente? A proposito ovviamente userete le tecniche di partizionamento dello spazio (BSP, Octree, quadtree, Tilemap ecc.) Per accelerare il gioco, ma dovrete sempre farlo comunque (se la mappa è grande), la domanda non è che comunque, ma sull'algoritmo utilizzato per spostare (correttamente) il giocatore.
Valmond,

@Valmond Penso che @Jeff significhi che se il tuo giocatore si sposta di 10 pixel a sinistra, puoi avere fino a 10 diversi rilevamenti di collisioni.
Jonathan Connell,

Se questo è ciò che intende, allora ha ragione e dovrebbe essere così (chi altrimenti può dire se ci fermiamo a +6 e non a +7?). I computer sono veloci, l'ho usato sull'S40 (~ 200mhz e un kvm non così veloce) e ha funzionato come un fascino. Puoi sempre provare a ottimizzare l'algo iniziale, ma questo ti darà sempre casi angolari come quello dell'OP.
Valmond,

È esattamente quello che intendevo dire
Jeff

2

Ho alcune domande prima di iniziare a rispondere a questa domanda. Innanzitutto, nell'insetto originale in cui ti sei bloccato nei muri, quelle tessere sulle singole tessere di sinistra erano contrapposte a una tessera grande? E se lo fossero, il giocatore si sarebbe bloccato tra di loro? Se sì a entrambe queste domande, assicurati solo che la tua nuova posizione sia valida . Ciò significa che dovrai verificare se c'è una collisione su dove stai dicendo al giocatore di muoversi. Quindi risolvi lo spostamento minimo come descritto di seguito, quindi sposta il tuo giocatore in base a quello solo se puòspostati là. Quasi troppo sotto il naso: P Questo introdurrà effettivamente un altro bug, che io chiamo "casi d'angolo". Essenzialmente in termini di angoli (come l'angolo in basso a sinistra dove escono le punte orizzontali nel tuo .gif, ma se non ci fossero punte) non risolverebbe una collisione, perché penserebbe che nessuna delle risoluzioni che generi porti ad una posizione valida . Per risolvere questo problema, tieni semplicemente d'occhio se la collisione è stata risolta, nonché un elenco di tutte le risoluzioni minime di penetrazione. Successivamente, se la collisione non è stata risolta, scorrere su ogni risoluzione generata e tenere traccia delle risoluzioni X e Y massime (i massimi non devono provenire dalla stessa risoluzione). Quindi risolvere la collisione su quei massimi. Questo sembra risolvere tutti i tuoi problemi e quelli che ho riscontrato.

    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;
        }

Un'altra domanda, sono le punte che mostri una tessera o singole tessere? Se si tratta di singole piastrelle sottili, potrebbe essere necessario utilizzare un approccio diverso per quelle orizzontali e verticali rispetto a quello che descrivo di seguito. Ma se sono intere tessere dovrebbe funzionare.


Va bene, quindi sostanzialmente è quello che descriveva @Trevor Powell. Dato che stai usando solo AABB, tutto ciò che devi fare è scoprire quanto un rettangolo penetra nell'altro. Questo ti darà una quantità nell'asse X e nella Y. Scegli il minimo tra i due e sposta l'oggetto che si scontra lungo quell'asse. Questo è tutto ciò che serve per risolvere una collisione AABB. Non dovrai MAI muoverti lungo più di un asse in una tale collisione, quindi non dovresti mai essere confuso su ciò che uno deve muovere per primo, poiché ti sposterai solo al minimo.

Il software Metanet ha un tutorial classico su un approccio qui . Va anche in altre forme.

Ecco una funzione XNA che ho creato per trovare il vettore di sovrapposizione di due rettangoli:

    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);
    }

(Spero che sia semplice da seguire, perché sono sicuro che ci sono modi migliori per implementarlo ...)

isCollision (Rectangle) è letteralmente solo una chiamata a Rectangle.Intersects di XNA (Rectangle).

L'ho provato con piattaforme mobili e sembra funzionare bene. Farò altri test più simili al tuo .gif per essere sicuro e riferire se non funziona.



Sono un po 'scettico di quel commento che ho fatto su di esso non lavorando con piattaforme sottili. Non riesco a pensare a un motivo per cui non lo farebbe. Inoltre, se vuoi la fonte, posso caricare un progetto XNA per te
Jeff,

Sì, lo stavo provando ancora un po 'e risale al tuo stesso problema di rimanere bloccato nel muro. È perché dove le due tessere si allineano tra loro, ti dice di spostarti verso l'alto a causa di quella inferiore, e poi fuori (proprio nel tuo caso) per quella superiore, negando efficacemente il movimento di gravità se la tua velocità y è abbastanza lenta . Dovrò cercare una soluzione per questo, mi sembra di ricordare di aver avuto questo problema prima.
Jeff,

Ok, ho escogitato un modo estremamente confuso per aggirare il tuo primo problema. La soluzione consiste nel trovare la penetrazione minima per ciascun AABB, risolvendo solo su quello con la X più grande, quindi un secondo passaggio risolvendo solo sulla Y più grande. Sembra male quando sei schiacciato, ma solleva il lavoro e puoi essere spinto orizzontalmente e il tuo problema di incollaggio originale è sparito. È hackey e non ho idea di come si tradurrebbe in altre forme. Un'altra possibilità potrebbe essere quella di saldare le geometrie toccanti, ma non ci ho nemmeno provato
Jeff

Le pareti sono realizzate con piastrelle 16x16. I picchi sono una tessera 16x16. Se risolvo sull'asse minimo, si verifica il bug di salto (guarda il diagramma). La tua soluzione "estremamente confusa" funzionerebbe con la situazione mostrata in .gif?
Vittorio Romeo,

Sì, funziona nel mio. Qual è la dimensione del tuo personaggio?
Jeff,

1

È semplice.

Riscrivi i picchi. È colpa dei picchi.

Le tue collisioni dovrebbero avvenire in unità discrete e tangibili. Il problema per quanto posso vedere è:

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

Il problema è con il passaggio 2, non 3 !!

Se stai cercando di far sembrare le cose solide, non dovresti lasciare che gli oggetti scivolino l'uno nell'altro in quel modo. Una volta che sei in una posizione di intersezione, se perdi il tuo posto, il problema diventa più difficile da risolvere.

I picchi dovrebbero idealmente verificare l'esistenza del giocatore e, quando si muovono, dovrebbero spingerlo come necessario.

Un modo semplice per raggiungere questo obiettivo è che il giocatore abbia moveXe moveYfunzioni che comprendano il paesaggio e spingeranno il giocatore di un certo delta o il più lontano possibile senza colpire un ostacolo .

Normalmente verranno chiamati dal loop degli eventi. Tuttavia possono anche essere chiamati da oggetti per spingere il giocatore.

function spikes going up:

    move spikes up

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

        player.moveY(-d)
    end if

end function

Ovviamente il giocatore ha ancora bisogno di reagire alle punte, se si va in loro .

La regola generale è, dopo che qualsiasi oggetto si sposta, dovrebbe terminare in una posizione senza intersezione. In questo modo, è impossibile ottenere i difetti che stai vedendo.


nel caso estremo in cui moveYnon riesce a rimuovere l'intersezione (perché è presente un altro muro) è possibile verificare nuovamente l'incrocio e provare a spostare X o semplicemente ucciderlo.
Chris Burt-Brown,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.