Come posso ottimizzare un motore di collisione in cui l'ordine è significativo e la collisione è condizionata in base al gruppo di oggetti?


14

Se questa è la prima volta su questa domanda, ti suggerisco di leggere prima la parte di pre-aggiornamento di seguito, quindi questa parte. Ecco una sintesi del problema, però:

Fondamentalmente, ho un motore di rilevamento e risoluzione delle collisioni con un sistema di partizionamento spaziale della griglia in cui sono importanti l'ordine di collisione e i gruppi di collisione. Un corpo alla volta deve muoversi, quindi rilevare la collisione, quindi risolvere le collisioni. Se muovo tutti i corpi contemporaneamente, quindi generi possibili coppie di collisione, è ovviamente più veloce, ma la risoluzione si interrompe perché l'ordine di collisione non viene rispettato. Se muovo un corpo alla volta, sono costretto a chiedere ai corpi di controllare le collisioni e questo diventa un problema ^ 2. Inserisci i gruppi nel mix e puoi immaginare perché diventa molto lento molto velocemente con molti corpi.


Aggiornamento: ho lavorato molto duramente su questo, ma non sono riuscito a ottimizzare nulla.

Ho implementato con successo il "dipinto" descritto da Will e ho cambiato i gruppi in bitset, ma è uno speedup molto minore.

Ho anche scoperto un grosso problema: il mio motore dipende dall'ordine di collisione.

Ho provato un'implementazione della generazione unica delle coppie di collisioni , che ha sicuramente accelerato molto di molto, ma ha rotto l' ordine di collisione .

Lasciatemi spiegare:

  • nel mio progetto originale (non generare coppie), ciò accade:

    1. si muove un solo corpo
    2. dopo che si è mosso, rinfresca le sue cellule e ottiene i corpi contro i quali si scontra
    3. se si sovrappone a un corpo che deve risolvere, risolvere la collisione

    questo significa che se un corpo si muove e colpisce un muro (o qualsiasi altro corpo), solo il corpo che si è mosso risolverà la sua collisione e l'altro corpo non sarà interessato.

    Questo è il comportamento che desidero .

    Capisco che non è comune per i motori fisici, ma ha molti vantaggi per i giochi in stile retrò .

  • nel solito disegno a griglia (che genera coppie uniche), ciò accade:

    1. tutti i corpi si muovono
    2. dopo che tutti i corpi si sono mossi, aggiorna tutte le celle
    3. generare coppie di collisioni uniche
    4. per ogni coppia, gestire il rilevamento e la risoluzione delle collisioni

    in questo caso una mossa simultanea avrebbe potuto causare la sovrapposizione di due corpi, e si risolveranno allo stesso tempo - questo fa sì che i corpi "si spingano l'un l'altro" e rompe la stabilità della collisione con più corpi

    Questo comportamento è comune per i motori fisici, ma non è accettabile nel mio caso .

Ho anche trovato un altro problema, che è importante (anche se non è probabile che accada in una situazione del mondo reale):

  • considerare i corpi dei gruppi A, B e W
  • A si scontra e si risolve contro W e A
  • B si scontra e si risolve contro W e B
  • A non fa nulla contro B
  • B non fa nulla contro A

può esserci una situazione in cui molti corpi A e corpi B occupano la stessa cellula - in quel caso, c'è molta iterazione non necessaria tra corpi che non devono reagire tra loro (o rilevare solo la collisione ma non risolverli) .

Per 100 corpi che occupano la stessa cella, sono 100 ^ 100 iterazioni! Ciò accade perché non vengono generate coppie uniche , ma non riesco a generare coppie uniche , altrimenti otterrei un comportamento che non desidero.

C'è un modo per ottimizzare questo tipo di motore di collisione?

Queste sono le linee guida che devono essere rispettate:

  • L'ordine di collisione è estremamente importante!

    • I corpi devono muoversi uno alla volta , quindi controllare le collisioni uno alla volta e risolvere dopo il movimento uno alla volta .
  • I corpi devono avere 3 bitset di gruppo

    • Gruppi : gruppi a cui appartiene il corpo
    • GroupsToCheck : gruppi contro i quali il corpo deve rilevare la collisione
    • GroupsNoResolve : gruppi contro i quali il corpo non deve risolvere la collisione
    • Ci possono essere situazioni in cui voglio solo rilevare una collisione ma non risolverla



Pre-update:

Premessa : sono consapevole che l'ottimizzazione di questo collo di bottiglia non è una necessità: il motore è già molto veloce. Tuttavia, per scopi educativi e divertenti, mi piacerebbe trovare un modo per rendere il motore ancora più veloce.


Sto creando un motore di rilevamento / risposta collisione 2D C ++ per scopi generali, con particolare attenzione alla flessibilità e alla velocità.

Ecco un diagramma di base della sua architettura:

Architettura di base del motore

Fondamentalmente, la classe principale è World, che possiede (gestisce la memoria) di a ResolverBase*, a SpatialBase*e a vector<Body*>.

SpatialBase è una pura classe virtuale che si occupa del rilevamento di collisioni a fase larga.

ResolverBase è una pura classe virtuale che si occupa della risoluzione delle collisioni.

I corpi comunicano World::SpatialBase*con gli SpatialInfooggetti, di proprietà dei corpi stessi.


Attualmente esiste una classe spaziale: Grid : SpatialBaseche è una griglia 2D fissa di base. Essa ha il proprio Informazioni classe, GridInfo : SpatialInfo.

Ecco come appare la sua architettura:

Architettura del motore con griglia spaziale

La Gridclasse possiede un array 2D di Cell*. La Cellclasse contiene una raccolta di (non di proprietà) Body*: a vector<Body*>che contiene tutti i corpi che si trovano nella cella.

GridInfo gli oggetti contengono anche puntatori non proprietari delle celle in cui si trova il corpo.


Come ho già detto, il motore si basa su gruppi.

  • Body::getGroups()restituisce uno std::bitsetdi tutti i gruppi di cui fa parte il corpo.
  • Body::getGroupsToCheck()restituisce uno std::bitsetdi tutti i gruppi contro i quali il corpo deve verificare la collisione.

I corpi possono occupare più di una singola cella. GridInfo memorizza sempre puntatori non proprietari sulle celle occupate.


Dopo che un singolo corpo si muove, si verifica il rilevamento delle collisioni. Presumo che tutti i corpi siano scatole di delimitazione allineate agli assi.

Come funziona il rilevamento di collisioni a fase larga:

Parte 1: aggiornamento delle informazioni spaziali

Per ciascuno Body body:

    • Vengono calcolate la cella occupata più in alto a sinistra e le celle occupate più in basso a destra.
    • Se differiscono dalle celle precedenti, body.gridInfo.cellsvengono cancellate e riempite con tutte le celle occupate dal corpo (2D per loop dalla cella più in alto a sinistra alla cella in basso a destra).
  1. body ora è garantito per sapere quali celle occupa.

Parte 2: controlli di collisione effettivi

Per ciascuno Body body:

  • body.gridInfo.handleCollisions è chiamato:

void GridInfo::handleCollisions(float mFrameTime)
{
    static int paint{-1};
    ++paint;

    for(const auto& c : cells)
        for(const auto& b : c->getBodies())
        {
            if(b->paint == paint) continue;
            base.handleCollision(mFrameTime, b);
            b->paint = paint;
        }
}

void Body::handleCollision(float mFrameTime, Body* mBody)
    {
        if(mBody == this || !mustCheck(*mBody) || !shape.isOverlapping(mBody->getShape())) return;

        auto intersection(getMinIntersection(shape, mBody->getShape()));

        onDetection({*mBody, mFrameTime, mBody->getUserData(), intersection});
        mBody->onDetection({*this, mFrameTime, userData, -intersection});

        if(!resolve || mustIgnoreResolution(*mBody)) return;
        bodiesToResolve.push_back(mBody);
    }

  • La collisione viene quindi risolta per ogni corpo in bodiesToResolve.

  • Questo è tutto.


Quindi, da un po 'di tempo cerco di ottimizzare questo rilevamento di collisioni a fase larga. Ogni volta che provo qualcos'altro rispetto all'attuale architettura / configurazione, qualcosa non va come previsto o faccio ipotesi sulla simulazione che in seguito si dimostreranno false.

La mia domanda è: come posso ottimizzare la fase larga del mio motore di collisione ?

Esiste una sorta di ottimizzazione magica del C ++ che può essere applicata qui?

L'architettura può essere riprogettata per consentire maggiori prestazioni?


Uscita Callgrind per l'ultima versione: http://txtup.co/rLJgz


Profilare e identificare i colli di bottiglia. Facci sapere dove sono, quindi abbiamo qualcosa su cui lavorare.
Maik Semder,

@MaikSemder: l'ho fatto e l'ho scritto nel post. È l'unico frammento di codice che è il collo di bottiglia. Scusate se è lungo e dettagliato, ma fa parte della domanda perché sono sicuro che questo collo di bottiglia può essere risolto solo modificando qualcosa nella progettazione del motore.
Vittorio Romeo,

Spiacente, è stato difficile da trovare. Puoi darci dei numeri? Il tempo della funzione e il numero di oggetti elaborati in quella funzione?
Maik Semder,

@MaikSemder: testato con Callgrind, su un binario compilato con Clang 3.4 SVN -O3: 10000 corpi dinamici - la funzione è getBodiesToCheck()stata chiamata 5462334 volte e ha impiegato il 35,1% dell'intero tempo di profilazione (tempo di accesso alla lettura delle istruzioni)
Vittorio Romeo

2
@Quonux: nessuna offesa. Ho appena amo "reinventare la ruota". Potrei prendere Bullet o Box2D e fare un gioco con quelle librerie, ma non è proprio questo il mio obiettivo. Mi sento molto più appagato e imparo molto di più creando cose da zero e cercando di superare gli ostacoli che appaiono - anche se ciò significa essere frustrati e chiedere aiuto. Oltre alla mia convinzione che la codifica da zero sia preziosa ai fini dell'apprendimento, trovo anche molto divertente e un grande piacere passare il mio tempo libero.
Vittorio Romeo,

Risposte:


14

getBodiesToCheck()

Potrebbero esserci due problemi con la getBodiesToCheck()funzione; primo:

if(!contains(bodiesToCheck, b)) bodiesToCheck.push_back(b);

Questa parte è O (n 2 ) non è vero?

Invece di controllare per vedere se il corpo è già nell'elenco, usa invece la pittura .

loop_count++;
if(!loop_count) { // if loop_count can wrap,
    // you just need to iterate all bodies to reset it here
}
bodiesToCheck.clear();
for(const auto& q : queries)
    for(const auto& b : *q)
        if(b->paint != loop_count) {
            bodiesToCheck.push_back(b);
            b->paint = loop_count;
        }
return bodiesToCheck;

Stai dereferenziando il puntatore nella fase di raccolta, ma lo faresti comunque nella fase di test, quindi se hai abbastanza L1 non è un grosso problema. Puoi migliorare le prestazioni aggiungendo anche suggerimenti di pre-fetch al compilatore __builtin_prefetch, ad esempio , anche se è più facile con i for(int i=q->length; i-->0; )loop classici e simili.

È una semplice modifica, ma il mio secondo pensiero è che potrebbe esserci un modo più veloce per organizzare questo:

Tuttavia, è possibile passare a utilizzare bitmap , evitando l'intero bodiesToCheckvettore. Ecco un approccio:

Stai già usando le chiavi intere per i corpi, ma poi le cerchi in mappe e cose e tienili in giro. È possibile passare a un allocatore di slot, che in pratica è solo un array o un vettore. Per esempio:

class TBodyImpl {
   public:
       virtual ~TBodyImpl() {}
       virtual void onHit(int other) {}
       virtual ....
       const int slot;
   protected:
      TBodyImpl(int slot): slot(slot_) {}
};

struct TBodyBase {
    enum ... type;
    ...
    rect_t rect;
    TQuadTreeNode *quadTreeNode; // see below
    TBodyImpl* imp; // often null
};

std::vector<TBodyBase> bodies; // not pointers to them

Ciò significa che tutto ciò che è necessario per eseguire le collisioni effettive è nella memoria lineare compatibile con la cache e si esce al bit specifico dell'implementazione e lo si collega a uno di questi slot se ce n'è bisogno.

Per tenere traccia delle allocazioni in questo vettore di corpi, è possibile utilizzare una matrice di numeri interi come bitmap e utilizzare bit twiddling o __builtin_ffsecc. È super efficiente per spostarsi negli slot attualmente occupati o trovare uno slot non occupato nell'array. A volte puoi persino compattare l'array se diventa irragionevolmente grande e quindi i lotti vengono contrassegnati come eliminati, spostando quelli alla fine per riempire gli spazi vuoti.

controllare ogni collisione solo una volta

Se hai verificato se a si scontra con b , non è necessario verificare se anche b si scontra con a .

Dall'utilizzo di ID interi risulta che si evitano questi controlli con una semplice istruzione if. Se l'id di una potenziale collisione è inferiore o uguale all'id corrente da verificare, può essere ignorato! In questo modo, controllerai ogni volta solo ogni possibile accoppiamento; sarà più della metà del numero di controlli di collisione.

unsigned * bitmap;
int bitmap_len;
...

for(int i=0; i<bitmap_len; i++) {
  unsigned mask = bitmap[i];
  while(mask) {
      const int j = __builtin_ffs(mask);
      const int slot = i*sizeof(unsigned)*8+j;
      for(int neighbour: get_neighbours(slot))
          if(neighbour > slot)
              check_for_collision(slot,neighbour);
      mask >>= j;
  }

rispettare l'ordine delle collisioni

Invece di valutare una collisione non appena viene trovata una coppia, calcola la distanza da colpire e memorizzala in un mucchio binario . Questi heap sono il modo in cui in genere fai le code prioritarie nella ricerca dei percorsi, quindi è un codice di utilità molto utile.

Contrassegna ogni nodo con un numero progressivo, in modo da poter dire:

  • A 10 colpisce B 12 a 6
  • A 10 colpisce C 12 a 3

Ovviamente dopo aver raccolto tutte le collisioni, inizi a saltarle dalla coda delle priorità, prima prima. Quindi il primo che ottieni è A 10 colpisce C 12 a 3. Aumenta il numero di sequenza di ogni oggetto (i 10 bit), valuta la collisione, calcola i nuovi percorsi e memorizza le nuove collisioni nella stessa coda. La nuova collisione è A 11 colpisce B 12 a 7. La coda ora ha:

  • A 10 colpisce B 12 a 6
  • A 11 colpisce B 12 a 7

Quindi fai un salto dalla coda di priorità e la sua A 10 colpisce B 12 a 6. Ma vedi che A 10 è stantio ; A è attualmente alle 11. Quindi puoi scartare questa collisione.

È importante non preoccuparsi di cercare di eliminare tutte le collisioni stantie dall'albero; la rimozione da un heap è costosa. Basta scartarli quando li pop.

la griglia

Dovresti invece considerare l'utilizzo di un quadtree. È una struttura di dati molto semplice da implementare. Spesso vedi implementazioni che memorizzano punti ma preferisco archiviare rects e memorizzare l'elemento nel nodo che lo contiene. Ciò significa che per controllare le collisioni devi solo scorrere su tutti i corpi e, per ciascuno, controllarlo contro quei corpi nello stesso nodo quad-tree (usando il trucco di ordinamento descritto sopra) e tutti quelli nei nodi quad-tree parent. Il quad-albero è di per sé l'elenco delle possibili collisioni.

Ecco un semplice Quadtree:

struct Object {
    Rect bounds;
    Point pos;
    Object * prev, * next;
    QuadTreeNode * parent;
};

struct QuadTreeNode {
    Rect bounds;
    Point centre;
    Object * staticObjects;
    Object * movableObjects;
    QuadTreeNode * parent; // null if root
    QuadTreeNode * children[4]; // null for unallocated children
};

Conserviamo gli oggetti mobili separatamente perché non dobbiamo verificare se gli oggetti statici si scontreranno con qualcosa.

Stiamo modellando tutti gli oggetti come scatole di delimitazione allineate agli assi (AABB) e li inseriamo nel più piccolo QuadTreeNode che li contiene. Quando un QuadTreeNode molti bambini, è possibile suddividerlo ulteriormente (se quegli oggetti si distribuiscono bene nei bambini).

Ogni tick di gioco, è necessario ricorrere al quadrifoglio e calcolare la mossa - e le collisioni - di ciascun oggetto mobile. Deve essere controllato per le collisioni con:

  • ogni oggetto statico nel suo nodo
  • ogni oggetto mobile nel suo nodo che è prima di esso (o dopo di esso; basta scegliere una direzione) nell'elenco movableObjects
  • ogni oggetto mobile e statico in tutti i nodi principali

Ciò genererà tutte le possibili collisioni, non ordinate. Quindi fai le mosse. Devi dare la priorità a queste mosse per distanza e "chi si muove per primo" (che è il tuo requisito speciale), ed eseguirle in questo ordine. Usa un heap per questo.

È possibile ottimizzare questo modello quadtree; non è necessario archiviare effettivamente i limiti e il punto centrale; è interamente derivabile quando cammini sull'albero. Non è necessario verificare se un modello rientra nei limiti, controllare solo da quale parte si trova il punto centrale (un test "asse di separazione").

Per modellare cose che volano velocemente come proiettili, piuttosto che spostarle ad ogni passo o avere un elenco separato di "proiettili" che controlli sempre, mettili semplicemente in quadriciclo con il retto del loro volo per un certo numero di fasi del gioco. Ciò significa che si muovono nel quadrifoglio molto più raramente, ma non stai controllando proiettili contro muri lontani, quindi è un buon compromesso.

Gli oggetti statici di grandi dimensioni devono essere suddivisi in parti componenti; un cubo di grandi dimensioni dovrebbe avere ciascuna faccia memorizzata separatamente, per esempio.


"Dipingere" suona bene, ci proverò e riferirò i risultati il ​​prima possibile. Tuttavia, non capisco la seconda parte della tua risposta: proverò a leggere qualcosa sul pre-recupero.
Vittorio Romeo,

Non consiglierei QuadTree, è più complicato che fare una griglia e, se non fatto correttamente, non funzionerà in modo accurato e creerà / rimuoverà nodi troppo spesso.
ClickerMonkey il

Informazioni sul tuo mucchio: l' ordine di movimento è rispettato? Considerare il corpo A e il corpo B . A si sposta verso destra verso B, e B si sposta verso destra verso A. Ora, quando si scontrano simultaneamente, quello che si muoveva per primo dovrebbe essere risolto per primo , e l'altro non ne risentirebbe.
Vittorio Romeo,

@VittorioRomeo se A si sposta verso B e B si sposta verso A con lo stesso segno di spunta, e alla stessa velocità, si incontrano nel mezzo? Oppure A, muovendosi per prima, incontra B dove inizia B?
Will


3

Scommetto che hai solo un sacco di errori nella cache quando si scorre sui corpi. Metti insieme tutti i tuoi corpi usando uno schema di progettazione orientato ai dati? Con una larga fase N ^ 2 posso simulare centinaia e centinaia , registrando con frappole, di corpi senza cadute di framerate nelle regioni inferiori (meno di 60), e tutto ciò senza un allocatore personalizzato. Immagina cosa si può fare con un corretto utilizzo della cache.

L'indizio è qui:

const std::vector<Body *>

Ciò solleva immediatamente un'enorme bandiera rossa. Stai assegnando questi corpi a nuove chiamate non elaborate? C'è un allocatore personalizzato in uso? È molto importante che tu abbia tutti i tuoi corpi in una vasta gamma in cui attraversi in modo lineare . Se attraversare la memoria in modo lineare non è qualcosa che ritieni di poter implementare, prendi in considerazione l'utilizzo di un elenco collegato in modo intrusivo.

Inoltre sembra che tu stia usando std :: map. Sai come viene allocata la memoria all'interno di std :: map? Avrai una complessità O (lg (N)) per ogni query della mappa, e questo può probabilmente essere aumentato a O (1) con una tabella hash. Inoltre, anche la memoria allocata da std :: map andrà a schiantare la cache in modo orribile.

La mia soluzione è usare una tabella hash intrusiva al posto di std :: map. Un buon esempio di elenchi collegati in modo intrusivo e tabelle di hash intrusive è nella base di Patrick Wyatt all'interno del suo progetto coho: https://github.com/webcoyote/coho

Quindi, in breve, probabilmente dovrai creare alcuni strumenti personalizzati per te, vale a dire un allocatore e alcuni contenitori invadenti. Questo è il meglio che posso fare senza profilare il codice per me stesso.


"Stai assegnando questi corpi a nuove chiamate non elaborate?" Non sto chiamando esplicitamente newquando spingo i corpi sul getBodiesToCheckvettore - vuoi dire che sta accadendo internamente? C'è un modo per impedirlo pur avendo ancora una collezione di corpi di dimensioni dinamiche?
Vittorio Romeo,

std::mapnon è un collo di bottiglia - ricordo anche di aver provato dense_hash_sete di non aver ottenuto alcun tipo di prestazione.
Vittorio Romeo,

@Vittorio, quale parte di getBodiesToCheck è il collo di bottiglia? Abbiamo bisogno di informazioni per aiutare.
Maik Semder,

@MaikSemder: il profiler non va più in profondità della funzione stessa. L'intera funzione è il collo di bottiglia, perché viene chiamato una volta per frame per corpo. 10000 corpi = 10000 getBodiesToCheckchiamate per frame. Sospetto che la costante pulizia / spinta nel vettore sia il collo di bottiglia della funzione stessa. Anche il containsmetodo fa parte del rallentamento, ma poiché bodiesToChecknon contiene mai più di 8-10 corpi, dovrebbe essere così lento
Vittorio Romeo,

@Vittorio sarebbe bello se metti queste informazioni nelle domande, questo è un punto di svolta;) In particolare intendo la parte che getBodiesToCheck viene chiamata per tutti i corpi, quindi 10000 volte ogni fotogramma. Mi chiedo, hai detto che erano in gruppi, quindi perché metterli nei corpi ToCheck-array, se hai già le informazioni sul gruppo. Potresti approfondire quella parte, mi sembra un ottimo candidato per l'ottimizzazione.
Maik Semder,

1

Riduci il conteggio dei corpi per controllare ogni fotogramma:

Controlla solo i corpi che possono effettivamente muoversi. Gli oggetti statici devono essere assegnati alle celle di collisione solo una volta dopo essere stati creati. Ora controlla solo le collisioni per i gruppi che contengono almeno un oggetto dinamico. Ciò dovrebbe ridurre il numero di controlli per ogni frame.

Usa un quadrifoglio. Vedi la mia risposta dettagliata qui

Rimuovi tutte le allocazioni dal tuo codice fisico. Puoi usare un profiler per questo. Ma ho analizzato solo l'allocazione di memoria in C #, quindi non posso fare a meno con C ++.

In bocca al lupo!


0

Vedo due candidati problematici nella funzione collo di bottiglia:

Il primo è la parte "contiene" - questo è probabilmente il motivo principale del collo di bottiglia. Sta ripetendo i corpi già trovati per ogni corpo. Forse dovresti piuttosto usare un qualche tipo di hash_table / hash_map invece di vector. Quindi l'inserimento dovrebbe essere più veloce (con la ricerca di duplicati). Ma non conosco numeri specifici - non ho idea di quanti corpi siano iterati qui.

Il secondo problema potrebbe essere vector :: clear e push_back. Clear può o meno evocare la riallocazione. Ma potresti voler evitarlo. La soluzione potrebbe essere un array di flag. Ma probabilmente potresti avere molti oggetti, quindi è inefficace la memoria avere un elenco di tutti gli oggetti per ogni oggetto. Qualche altro approccio potrebbe essere carino, ma non so quale approccio: /


Informazioni sul primo problema: ho provato a usare un dense_hash_set invece del vettore + contiene, ed è stato più lento. Ho provato a riempire il vettore e quindi a rimuovere tutti i duplicati, ed è stato più lento.
Vittorio Romeo,

0

Nota: non so nulla di C ++, solo Java, ma dovresti essere in grado di capire il codice. La fisica è il linguaggio universale giusto? Mi rendo anche conto che questo è un post di un anno, ma volevo solo condividerlo con tutti.

Ho un modello di osservatore che sostanzialmente, dopo che l'entità si muove, restituisce l'oggetto con cui si è scontrato, incluso un oggetto NULL. In poche parole:

( Sto rifacendo Minecraft )

public Block collided(){
   return World.getBlock(getLocation());
}

Quindi supponiamo che tu vada in giro nel tuo mondo. ogni volta che chiami move(1), poi chiama collided(). se ottieni il blocco che desideri, forse le particelle volano e puoi muoverti a sinistra a destra e indietro ma non in avanti.

Usando questo in modo più generico rispetto a Minecraft come esempio:

public Object collided(){
   return threeDarray[3][2][3];
}

Semplicemente, disponi di un array per indicare le coordinate che, letteralmente, come fa Java, usa i puntatori.

L'uso di questo metodo richiede ancora qualcosa di diverso dal metodo a priori per il rilevamento delle collisioni. Potresti ripetere questo, ma questo sconfigge lo scopo. Puoi applicarlo alle tecniche di collisione larga, media e stretta, ma da solo, è una bestia soprattutto quando funziona abbastanza bene per i giochi 3D e 2D.

Dando un'altra occhiata, questo significa che, secondo il mio metodo collec () di Minecraft, finirò all'interno del blocco, quindi dovrò spostare il giocatore fuori da esso. Invece di controllare il giocatore, devo aggiungere un rettangolo di selezione che controlla quale blocco colpisce ciascun lato del riquadro. Problema risolto.

il paragrafo precedente potrebbe non essere così semplice con i poligoni se si desidera la precisione. Per precisione, suggerirei di definire un rettangolo di selezione poligonale che non sia un quadrato, ma non tassellato. in caso contrario, un rettangolo va bene.

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.