Come posso evitare che il personaggio del mio platform si ritagli sui rivestimenti?


9

Attualmente, ho un platform con piastrelle per terreno (grafica presa in prestito da Cave Story). Il gioco è scritto da zero usando XNA, quindi non sto usando un motore esistente o un motore fisico.

Le collisioni di piastrelle sono descritte praticamente esattamente come descritto in questa risposta (con semplice SAT per rettangoli e cerchi), e tutto funziona bene.

Tranne quando il giocatore si imbatte in un muro mentre cade / salta. In tal caso, afferreranno una piastrella e inizieranno a pensare di aver colpito un pavimento o un soffitto che non è effettivamente lì.

Immagine della cattura del personaggio

In questa schermata, il giocatore si sta muovendo a destra e sta cadendo verso il basso. Quindi, dopo il movimento, vengono verificate le collisioni - e per prima cosa, il personaggio del giocatore si scontra con la tessera 3 ° dal pavimento e spinto verso l'alto. In secondo luogo, si è trovato a scontrarsi con la tessera accanto a lui, e ha spinto lateralmente - il risultato finale è che il personaggio del giocatore pensa di essere a terra e non sta cadendo, e 'prende' sulla tessera fintanto che ci si imbatte .

Potrei risolverlo definendo invece le tessere dall'alto verso il basso, il che lo fa cadere senza intoppi, ma poi succede il caso inverso e colpirà un soffitto che non è lì quando salta verso l'alto contro il muro.

Come dovrei avvicinarmi a risolverlo, in modo che il personaggio del giocatore possa semplicemente cadere lungo il muro come dovrebbe?



@ Byte56 La domanda non è la stessa, ma la sua risposta potrebbe aiutare.
doppelgreener,

Una nota: ho avuto pochissimo tempo nelle ultime due settimane per sperimentare le risposte qui. Ho implementato la risoluzione delle collisioni mondiali come risposto nella domanda @ Byte56 collegata, che ha bug separati ad alta velocità e non sono ancora stato in grado di provare le risposte a questa domanda. Proverò quelli quando posso e accetterò una risposta quando posso, o pubblicherò la mia se la risolverò da solo.
doppelgreener,

Risposte:


8

L'approccio più semplice e più a prova di errore è semplicemente quello di non verificare la presenza di collisioni sui bordi nascosti. Se hai due tessere muro, una direttamente sopra l'altra, il bordo inferiore della tessera superiore e il bordo superiore della tessera inferiore non devono essere controllati per la collisione con il giocatore.

È possibile determinare quali spigoli sono validi durante un semplice passaggio sulla mappa delle tessere, memorizzando i bordi esterni come flag in ciascuna posizione delle tessere. Puoi anche farlo in fase di esecuzione controllando i riquadri adiacenti durante il rilevamento delle collisioni.

Esistono versioni più avanzate di questa stessa tecnica che rendono più facile e veloce supportare anche le piastrelle non quadrate.

Ti consiglio di leggere gli articoli qui sotto. Sono semplici e discrete introduzioni a fare fisica di base e rilevamento delle collisioni nei moderni platform (come Cave Story). Se stai cercando una sensazione di Mario più vecchia scuola, ci sono alcuni trucchi di cui preoccuparsi, ma la maggior parte di essi prevede la gestione di salti e animazioni piuttosto che collisioni.

http://www.metanetsoftware.com/technique/tutorialA.html http://www.metanetsoftware.com/technique/tutorialB.html


Puoi chiarire come ignorare alcuni bordi durante la collisione? Di solito vedo che le collisioni vengono rilevate come l'intersezione di due rettangoli (i limiti del giocatore e i limiti delle tessere). Quando dici di non controllare la collisione contro i singoli bordi, significa che l'algoritmo di collisione non è un'intersezione rettangolo-rettangolo ma qualcos'altro? La tua descrizione ha senso, ma non sono sicuro di come sarebbe l'implementazione.
David Gouveia,

Sì, fai solo una collisione a linea rettangolare. Semplificato solo perché la linea è sempre allineata agli assi. Puoi semplicemente scomporre il check box-box.
Sean Middleditch l'

5

Qui l'ordine farei le cose ....

1) Salva l'attuale X, Y del personaggio

2) Spostare la direzione X.

3) Controlla tutti gli angoli del personaggio ottenendo i dati delle tessere e controlla se sono solidi nel modo seguente: (X e Y è la posizione del personaggio)

  • per in alto a sinistra: floor (X / tileWidth), floor (Y / tileHeight);
  • per in alto a destra: floor ((X + characterWidth) / tileWidth), floor (Y / tileHeight);
  • per in basso a sinistra: floor (X + / tileWidth), floor ((Y + characterHeight) / tileHeight);
  • per in basso a destra: floor ((X + characterWidth) / tileWidth), floor ((Y + characterHeight) / tileHeight);

... per ottenere i dati delle tessere corretti dai dati della mappa

4) Se una delle tessere è solida, allora devi forzare X nella posizione migliore ripristinando la X salvata e ...

  • se ti muovi a sinistra: X = floor (X / tileWidth) * tileWidth;
  • se ti muovi a destra: X = ((floor ((X + characterWidth) / tileWidth) + 1) * tileWidth) - characterWidth;

5) Ripeti 2-4 usando Y

Se il tuo personaggio è più alto di una tessera, devi controllare più tessere. Per fare questo è necessario eseguire il loop e aggiungere tileHeight alla posizione carattereY per ottenere il riquadro

int characterBlockHeight = 3;

// check all tiles and intermediate tiles down the character body
for (int y = 0; y < characterBlockHeight - 1; y++) {
    tile = getTile(floor(characterX / tileWidth), floor((characterY + (y * tileHeight)) / tileHeight));
    ... check tile OK....
}

// finally check bottom
tile = getTile(floor(characterX / tileWidth), floor((characterY + characterHeight) / tileHeight);
... check tile OK....

Se il carattere è più largo di 1 blocco, fai lo stesso ma sostituisci characterY, characterHeight, tileHeight, ecc. Con characterX, characterWidth, ecc.


2

Cosa succede se si verifica la possibilità di una collisione come parte del metodo di movimento del giocatore e si rifiuta il movimento che ha causato lo scivolamento del giocatore in un altro oggetto? Ciò impedirebbe al giocatore di entrare "all'interno" di un blocco, e quindi non potrebbe essere "in cima" al blocco sottostante.


1

Ho riscontrato quasi lo stesso identico problema in un gioco a cui sto lavorando attualmente. Il modo in cui l'ho risolto è stato quello di memorizzare l'area della parte sovrapposta durante il test per le collisioni e quindi gestire quelle collisioni in base alla loro priorità (prima l'area più grande), quindi ricontrollare ciascuna collisione per vedere se era già stata risolta da un precedente la manipolazione.

Nel tuo esempio, l'area della collisione dell'asse x sarebbe maggiore dell'asse y, quindi verrà gestita per prima. Quindi, prima di tentare di gestire la collisione dell'asse y, verificherebbe che non si scontrerebbe più dopo essere stato spostato sull'asse x. :)


1

Una soluzione semplice, ma non perfetta, è quella di cambiare la forma della scatola di collisione del giocatore in modo che non sia un quadrato. Taglia gli angoli per renderlo simile a un ottagono o qualcosa di simile. In questo modo, quando si scontra con una piastrella come quelle nel muro, scivolerà via da essa e non verrà catturato. Questo non è perfetto perché puoi ancora notare che sta giocando un po 'in curva, ma non si bloccherà ed è molto facile da implementare.


1

In questo tutorial è stato affrontato un problema molto simile: Introduzione allo sviluppo di giochi . La parte rilevante del video è alle 1:44 , spiegando con diagrammi e frammenti di codice.

Avviso 14-tilemap.py presenta il problema di ritaglio, ma è stato risolto in 15-blocker-sides.py

Non riesco a spiegare esattamente perché l'ultimo esempio del tutorial non si interrompa mentre il codice lo fa, ma penso che abbia qualcosa a che fare con:

  • Risposta di collisione condizionale in base al tipo di piastrella (quali bordi sono effettivamente attivi).
  • Il giocatore "cade sempre". Sebbene il giocatore sembri appoggiato su una tessera, il giocatore in realtà cade ripetutamente attraverso la tessera, quindi viene spinto in cima. (Il self.restingmembro nel codice viene utilizzato solo per verificare se il giocatore può saltare. Nonostante ciò, non riesco a fare un salto "doppio" da un muro. Teoricamente potrebbe essere possibile, però)

0

Un altro metodo (a cui ho risposto alla domanda collegata Byte56) sarebbe quello di verificare se la risoluzione della collisione mette il personaggio in un punto vuoto o meno. Quindi, nel tuo problema, ottieni una collisione dal soffitto del rettangolo interno per spostarlo verso l'alto, il che sarebbe illegale (poiché sei ancora in collisione con un'altra tessera). Invece lo sposti solo se sei spostato in uno spazio libero (ad esempio come la collisione dalla tessera superiore ti sposterà a sinistra) e, una volta trovata quella collisione, hai finito.

La risposta che ho dato ha avuto il codice, ma è diventato eccessivamente complicato. Terrei traccia di tutte le collisioni entro quel lasso di tempo e prenderei solo quello che porta a uno spazio libero. Tuttavia, se non ce ne fossero, penso di aver fatto ricorso al ripristino della posizione del personaggio nella sua ultima posizione, tuttavia è davvero brutto e forse preferiresti provare a implementare qualcosa come il Mario originale in cui si muove in una direzione o qualcosa in cui non c'è spazio libero è possibile. Inoltre, è possibile ordinare l'elenco delle risoluzioni delle collisioni e spostarsi nello spazio libero con la distanza più breve (penso che quella soluzione sarebbe la più preferibile, anche se non ho programmato questo).


0

Ho fatto solo 3 SAT in 3D, quindi forse non sto andando bene. Se capisco il tuo problema, potrebbe essere dovuto a contatti con un asse di contatto che non dovrebbero essere possibili, con conseguente azione del giocatore come se ci fosse un piano. Una soluzione alternativa potrebbe essere quella di utilizzare una forma circolare per il tuo personaggio, quindi otterrai solo gli assi di contatto nella direzione dell'asse x quando si colpisce un muro.

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.