Problemi di rilevamento delle collisioni con AABB


8

Ho implementato una semplice routine di rilevamento delle collisioni usando AABB tra il mio sprite di gioco principale e varie piattaforme (vedi il codice sotto). Funziona benissimo, ma ora sto introducendo la gravità per far cadere il mio personaggio e ha mostrato alcuni problemi con la mia routine di CD.

Penso che il nocciolo della questione sia che la mia routine di CD sposta lo sprite indietro lungo l'asse in cui è penetrato il minimo nell'altro sprite. Quindi, se è più nell'asse Y che nella X, lo sposta indietro lungo l'asse X.

Tuttavia, ora il mio sprite (eroe) ora sta cadendo a una velocità di 30 pixel per fotogramma (è uno schermo alto 1504 - ne ho bisogno per cadere così velocemente come voglio provare a simulare la gravità "normale", qualsiasi più lento sembra strano ) Ricevo questi problemi. Proverò a mostrare ciò che sta accadendo (e ciò che penso stia causando - anche se non sono sicuro) con alcune immagini: (Codice sotto le immagini).

Gradirei alcuni suggerimenti su come aggirare questo problema.

inserisci qui la descrizione dell'immagine

Per un chiarimento, nella figura in alto a destra, quando dico che la posizione è corretta "in modo errato", forse è un po 'fuorviante. Il codice stesso funziona correttamente per come è scritto o, in altre parole, l'algoritmo stesso se si comporta come mi aspetterei, ma devo cambiare il suo comportamento per fermare questo problema, spero che chiarisca i miei commenti nella foto .

inserisci qui la descrizione dell'immagine

Il mio codice

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

Per elaborare, perché si sposta gli sprite sull'altro asse: //One sprite has penetrated into the other, mostly in the Y axis, so move sprite //back on the X Axis
Tom 'Blue' Piddock

Ciao @Blue, perché lo sprite dovrebbe essere corretto lungo l'asse meno penetrante, quindi se lo sprite ha penetrato la piattaforma più nell'asse X rispetto a Y, è Y che dovrebbe essere corretto in quanto è il minore dei due.
BungleBonce,


Ho letto quella domanda prima di @Anko - non mi dà le risposte di cui ho bisogno. Quindi ho composto la mia domanda (non sono convinto che il richiedente stia chiedendo esattamente la stessa cosa, anche se è difficile esserne certi) :-)
BungleBonce,

Risposte:


1

durante i miei esperimenti con HTML5 canvas e AABB ho trovato esattamente quello che stai vivendo. Questo è successo quando ho tentato di creare una piattaforma da scatole adiacenti di 32x32 pixel.

Soluzioni che ho tentato per ordine delle mie preferenze personali

1 - Dividi i movimenti per asse

Il mio attuale, e penso che continuerò il mio gioco con esso. Ma assicurati di controllare quello che chiamo Phantom AABB se hai bisogno di una soluzione rapida senza dover modificare molto il tuo progetto attuale.

Il mio mondo di gioco ha una fisica molto semplice. Non esiste un concetto di forze (ancora), solo uno spostamento. La gravità è uno spostamento costante verso il basso. Nessuna accelerazione (ancora).

Lo spostamento viene applicato per asse. E in questo consiste questa prima soluzione.

Ogni frame di gioco:

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

move_x viene impostato in base all'elaborazione dell'input, se non viene premuto il tasto freccia, move_x = 0. Valori sotto zero significano sinistra, altrimenti a destra.

Elabora tutti gli altri sprite in movimento e decidi i valori del loro componente "mosse", hanno anche il componente "mosse".

Nota: dopo il rilevamento e la risposta delle collisioni, il componente di spostamento deve essere impostato su zero, entrambe le proprietà x e y, poiché al successivo tick verrà aggiunta nuovamente la gravità. Se non si reimposta, si termina con una mossa. Di due volte la gravità desiderata e nel segno di spunta successivo saranno tre volte.

Quindi immettere il codice di applicazione dello spostamento e il rilevamento delle collisioni.

Il componente move non è in realtà la posizione corrente dello sprite. Lo sprite non si è ancora spostato anche se move.x o y è cambiato. Il componente "mosse" è un modo per salvare lo spostamento da applicare alla posizione di sprite nella parte corretta del loop di gioco.

Elabora prima l'asse y (move.y). Applica move.y a sprite.position.y. Quindi applica il metodo AABB per vedere se lo sprite in movimento in fase di elaborazione si sovrappone a una piattaforma AABB (hai già capito come farlo). Se si sovrappone, spostare indietro lo sprite nell'asse y applicando penetration.y. Sì, a differenza della mia altra risposta, ora questo metodo evoluto di mossa ignora la penetration.x, per questa fase del processo. E usiamo la penetrazione.y anche se è maggiore della penetrazione.x, non stiamo nemmeno verificando.

Quindi elaborare l'asse x (moves.x). Applica move.x a sprite.position.x. E fare il contrario di prima. Questa volta lo sprite verrà spostato solo sull'asse orizzontale applicando penetration.x. Come prima, non ti interessa se penetration.x è minore o maggiore di penetration.y.

Il concetto qui è che se lo sprite non si muove e inizialmente non si scontra, rimarrà così nel frame successivo (sprite.moves.xey sono zero). Il caso speciale in cui un altro oggetto di gioco si teletrasporta magicamente in una posizione che si sovrappone allo sprite inizia elaborato è qualcosa che tratterò in seguito.

Quando uno sprite inizia a muoversi. Se si muove solo nell'asse x, allora siamo interessati solo se penetra in qualcosa a sinistra o a destra. Poiché lo sprite non si sta spostando verso il basso e lo sappiamo perché la proprietà y del componente move è zero, non controlliamo nemmeno il valore calcolato per la penetrazione.y, vediamo solo in penetration.x. Se la penetrazione esiste nell'asse del movimento, applichiamo la correzione, altrimenti lasciamo semplicemente muovere lo sprite.

Che dire dello spostamento diagonale, non necessariamente in un angolo di 45 gradi?

Il sistema proposto può gestirlo. Devi solo elaborare ciascun asse separatamente. Durante la tua simulazione. Forze diverse vengono applicate allo sprite. Anche se il tuo motore non capisce ancora le forze. Queste forze si traducono in uno spostamento arbitrario nel piano 2D, questo spostamento è un vettore 2D in qualsiasi direzione e di qualsiasi lunghezza. Da questo vettore è possibile estrarre l'asse y e l'asse x.

Cosa fare se il vettore di spostamento è troppo grande?

Non vuoi questo:

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

Poiché le nostre nuove mosse che applicano la logica elaborano prima un asse e poi l'altro, uno spostamento di grandi dimensioni potrebbe finire per essere visto dal codice di collisione come una grande L, questo non rileverà correttamente le collisioni con oggetti che si intersecano con il vettore di spostamento.

La soluzione è quella di dividere grandi spostamenti in piccoli passi, idealmente larghezza o altezza dello sprite, o metà della larghezza o altezza dello sprite. Per ottenere qualcosa del genere:

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

Che dire di teletrasportare gli sprite?

Se rappresenti il ​​teletrasporto come un grande spostamento, usando lo stesso componente di mosse come una mossa normale, allora nessun problema. In caso contrario, devi pensare a un modo per contrassegnare lo sprite di teletrasporto che deve essere elaborato dal codice di collisione (aggiungendo a un elenco dedicato a questo scopo?), Durante il codice di risposta puoi spostare direttamente lo sprite permanente che si trovava in il modo in cui si è verificato il teletrasporto.

2 - Phantom AABB

Avere un AABB secondario per ogni sprite influenzato dalla gravità che inizia nella parte inferiore dello sprite, ha la stessa larghezza dello sprite AABB e 1 pixel di altezza.

L'idea qui è che questo fantasma AABB si scontra sempre con la piattaforma sotto lo sprite. Solo quando questo fantasma AABB non si sovrappone a nulla, la gravità può essere applicata allo sprite.

Non è necessario calcolare il vettore di penetrazione per il fantasma AABB e la piattaforma. Ti interessa solo un semplice test di collisione, se si sovrappone a una piattaforma, la gravità non può essere applicata. Il tuo sprite può avere una proprietà booleana che dice se si trova su una piattaforma o in aria. Sei in aria quando il tuo fantasma AABB non si scontra con una piattaforma sotto il giocatore.

Questo:

player.position.y += gravity;

Diventa qualcosa del genere:

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

Molto semplice e affidabile.

Lascia l'implementazione AABB così com'è.

Il fantasma AABB può avere un loop dedicato che li elabora, che controlla solo per semplici collisioni e non perde tempo a calcolare il vettore di penetrazione. Questo loop deve essere prima del normale codice di collisione e risposta. Puoi applicare la gravità durante questo ciclo, poiché quegli sprite in aria applicano la gravità. Se il fantasma AABB è di per sé una classe o una struttura, può avere un riferimento allo sprite a cui appartiene.

Questo è molto simile all'altra soluzione proposta in cui controlli se il tuo giocatore sta saltando. Sto dando un modo possibile per rilevare se il giocatore ha i piedi su una piattaforma.


Mi piace la soluzione Phantom AABB qui. Ho già applicato qualcosa di simile per aggirare il problema. (Ma non usando un fantasma AABB) - Sono interessato a saperne di più sul tuo metodo iniziale sopra, ma ne sono un po 'confuso. Il componente Moves.x (e .y) è fondamentalmente la quantità da cui verrà spostato lo sprite (se è consentito spostarlo)? Inoltre, non sono chiaro come mi aiuterebbe direttamente con la mia situazione. Ti sarei grato se potessi modificare la tua risposta per mostrare un grafico in che modo potrebbe aiutarmi con la mia situazione (come nel mio grafico sopra) - grazie!
BungleBonce,

Informazioni sul componente mosse. Rappresenta lo spostamento futuro, sì, ma non si controlla se si consente il movimento o meno, si applica effettivamente il movimento, per asse, prima y, quindi x, e quindi si vede se si è scontrato con qualcosa, se si utilizza la penetrazione vettore per tornare indietro. Perché salvare lo spostamento per successive elaborazioni differisce dall'applicazione effettiva dello spostamento quando ci sono input dell'utente da elaborare o qualcosa è successo nel mondo di gioco? Proverò a inserirlo nelle immagini quando modifico la mia risposta. Nel frattempo vediamo se qualcun altro ha un'alternativa.
Hatoru Hansou,

Ah OK - Penso di aver capito cosa stai dicendo ora. Quindi, invece di spostare x & y e quindi controllare la collisione, con il tuo metodo, muovi prima X, quindi controlla la collisione, se si scontra quindi correggi lungo l'asse X. Quindi spostare Y e controllare la collisione, se c'è una collisione, quindi spostarsi lungo l'asse X? In questo modo conosciamo sempre l'asse di impatto corretto? Ho capito bene ?! Grazie!
BungleBonce,

Avvertenza: hai scritto "Quindi spostare Y e controllare la collisione, se c'è una collisione, quindi spostarsi lungo l'asse X?" Il suo asse Y. Probabilmente un errore di battitura. Sì, questo è il mio approccio attuale. Per spostamenti molto grandi, più della larghezza o dell'altezza del tuo sprite, devi dividere in passaggi più piccoli, e questo è ciò che intendo con l'analogia L. Attenzione a non applicare uno spostamento di grandi dimensioni nell'asse y e quindi una grande x, è necessario dividere entrambi in piccoli passaggi e intercalare per ottenere il comportamento corretto. Qualcosa di cui preoccuparsi solo se nel gioco sono consentiti grandi spostamenti. Nel mio, so che accadranno. Quindi mi sono preparato per questo.
Hatoru Hansou,

Brillante @HatoruHansou, non ho mai pensato di fare le cose in questo modo, sicuramente ci proverò - grazie mille per il tuo aiuto, segnerò la tua risposta come accettata. :-)
BungleBonce,

5

Combinerei le tessere piattaforma in un'unica entità piattaforma.

Ad esempio, supponi di prendere le 3 tessere dalle immagini e di combinarle in un'unica entità, la tua entità avrebbe una scatola di collisione di:

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

L'uso di quello per la collisione ti darà l'asse y come meno penetrante nella maggior parte della piattaforma, consentendoti comunque di avere tessere specifiche all'interno della piattaforma.


Ciao @UnderscoreZero - grazie, questa è una strada da percorrere, l'unica cosa è che sto attualmente conservando le mie tessere in 3 matrici separate, una per le tessere sul lato sinistro, su per le tessere centrali e una per le tessere sul lato destro - Immagino che potrei combinali tutti in un array ai fini del CD, ma non sono sicuro di come scrivere il codice per farlo.
BungleBonce,

Un'altra opzione che potresti provare che molti motori fisici usano è che una volta che l'oggetto è atterrato su un piano piatto, disabilitano la gravità per evitare che provi a cadere. Una volta che l'oggetto riceve una forza sufficiente per allontanarlo dal piano o cade dal lato del piano, riattivano la gravità ed eseguono la fisica come di consueto.
UnderscoreZero

0

Problemi come questi sono comuni con i nuovi metodi di rilevamento delle collisioni.

Non so molto della tua attuale configurazione di collisione, ma ecco come dovrebbe andare:

Prima di tutto, crea queste variabili:

  1. Crea una velocità X e Y.
  2. Crea una gravità
  3. Fai un salto Velocità
  4. Fai un salto

In secondo luogo, crea un timer per calcolare il delta per influenzare la velocità dell'oggetto.

Terzo, fai questo:

  1. Controlla se il giocatore sta premendo il pulsante di salto e non sta saltando, in tal caso aggiungi jumpSpeed ​​alla velocità Y.
  2. Sottrai la gravità dalla velocità Y ogni aggiornamento per simulare la gravità
  3. Se l'altezza y + del giocatore è inferiore o uguale alla y della piattaforma, imposta la velocità Y su 0 e metti la Y del giocatore sull'altezza y + della piattaforma.

Se lo fai, non ci dovrebbero essere problemi. Spero che sia di aiuto!


Ciao @ user1870398, grazie, ma al momento il mio gioco non ha nemmeno il salto, la domanda riguardava i problemi che sorgono attraverso l'uso del metodo dell'asse meno penetrante per correggere la posizione dello sprite. Il problema è che la gravità sta spingendo ulteriormente il mio personaggio nelle mie piattaforme lungo l'asse Y piuttosto che si sovrappone sull'asse X, quindi la mia routine (come è corretto per questo tipo di algoritmo) sta correggendo l'asse X, quindi non posso spostarsi lungo la piattaforma (poiché la routine del CD vede le singole tessere e le tratta come tali anche se sono presentate come un'unica piattaforma).
BungleBonce,

Ho il tuo problema. È solo che non stai correggendo correttamente il tuo movimento. Hai bisogno di una variabile isJumping . Se si preme il pulsante di salto e non si salta, lo si imposta su vero, se si colpisce il terreno lo si imposta su falso, spostare la Y del giocatore nella posizione corretta ( platform.y + player.height ) e impostare isJumping su false. Quindi, subito dopo aver verificato queste cose, fai if (tasto_ sinistra) player.velocity.x - = (player.isJumping? Player.speed: player.speed - player.groundFriction) . So che non hai ancora implementato il salto, quindi hai sempre impostato isJumping su false.
LiquidFeline,

Non ho salti, ma se una variabile 'ifJumping' è sempre impostata su false, come è diverso dal non averne una? (diciamo nei giochi in cui non c'è salto) Sono solo un po 'confuso su come un booleano "saltellante" possa aiutare nella mia situazione - ti sarei grato se potessi spiegare un po' di più - grazie!
BungleBonce,

Non è necessario. È meglio implementarlo mentre lo stai ancora programmando. Comunque, la ragione di isJumping è organizzare il tuo codice e prevenire i problemi aggiungendo gravità solo mentre il giocatore sta saltando. Fai solo quello che ho raccomandato. Questo metodo è quello che la maggior parte delle persone usa. Anche Minecraft lo usa! Ecco come viene simulata la gravità in modo efficace! :)
LiquidFeline
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.