Rilevamento efficiente delle collisioni basato su piastrelle per molti quadrati?


9

attualmente sto lavorando alla mia versione di un gioco basato su tessere (pensa Terraria, ma meno fantastico (penso che sia una parola? Scusa se non lo è)).

Ad ogni modo, attualmente sto lavorando al rilevamento delle collisioni (anche per casi angolari!) Che è stato un grande passo per me. C'è qualcosa di estremamente gratificante nel vedere uno sprite non attraversare un blocco. Ma poi ho avuto l'idea di fare un benchmark. Cattiva idea.

1.000 quadrati, nessun problema. 10.000 quadrati, per 3 personaggi era un po 'ritardato. 100.000 quadrati (mappa davvero enorme), per 3 personaggi era ingiocabile.

Sto riscontrando il problema per cui non voglio nemmeno prendere in considerazione i blocchi che sono troppo lontani dal giocatore, dai personaggi, dagli oggetti, ecc., Ma non voglio caricare costantemente questi in-out di memoria.

Ecco il mio algoritmo finora, sentiti libero di criticare.

foreach (Block in level)
{
    if (distance from block to player > a specified amount)
        ignore this block;
    else
    {
        get the intersection depth between the two bounding boxes
        if (depth of intersection != Zero-vector)
        {
            check y size vs x size
            resolve on smallest axis
        }
    }
}

Come noterai, quando la dimensione del livello aumenta, l'ordine di questo algoritmo cresce di N blocchi. Non vorrei nemmeno prendere in considerazione blocchi che non sono nemmeno vicini al giocatore.

Sto forse pensando di usare un doppio array di blocchi da (0,0) a (mapWidth, mapHeight) anziché un elenco, calcolando una zona di pericolo in base alla posizione della persona, ad es. Se la posizione del giocatore è a (10, 20) guarderà da (0, 10) a (20, 30) o così via.

Qualsiasi pensiero e considerazione sono fantastici, grazie.


1
E benvenuto su stackexchange! :-) Non dimenticare di leggere le FAQ se non sei a conoscenza di come funziona l'intero QA e il sistema di reputazione.
David Gouveia,

Sicuramente queste tessere sono più grandi di 16 per 16 pixel, a 1920 per 1080 sono 8.100 tessere. Sicuramente sai dove si trovano le entità mobili e puoi solo controllare i riquadri sulla griglia che possono essere nel raggio (se uno è 160 * 160 e il centro è nel riquadro (12,12) devi solo controllare tra i riquadri (6 , 6) e (18,18) per un totale di ~ 150 tessere possibili.). Sicuramente le tessere sotto gravità cadono e quindi devi solo cercare la tessera successiva sotto di essa.
DampeS8N,

Pensi che 16x16 sia troppo piccolo? Non sarebbe difficile per me cambiare la dimensione delle piastrelle, poiché tutto ciò che fa riferimento alla larghezza / altezza delle piastrelle è una costante statica. Tutto quello che dovrei fare è ingrandirli in Paint.NET, il che è bello perché aggiunge più dettagli.
Ross,

Ti dispiacerebbe condividere il tuo codice di collisione? : /
ashes999

Risposte:


7

Sì, stai pensando correttamente. Dovresti usare una matrice 2D di tessere poiché ti consente di indicizzare le tessere per posizione.

Block[,] level = new Block[width, height];

E poiché il giocatore può solo scontrarsi con le sue tessere circostanti, il numero di controlli di collisione che devi fare è molto piccolo. Questo ovviamente dipende dalle dimensioni del giocatore. L' esempio Platformer funziona in questo modo:

int leftTile = (int)Math.Floor((float)characterBounds.Left / tileWidth);
int rightTile = (int)Math.Ceiling(((float)characterBounds.Right / tileWidth)) - 1;
int topTile = (int)Math.Floor((float)characterBounds.Top / tileHeight);
int bottomTile = (int)Math.Ceiling(((float)characterBounds.Bottom / tileHeight)) - 1;

for (int y = topTile; y <= bottomTile; ++y)
{
    for (int x = leftTile; x <= rightTile; ++x)
    {
        // Handle collisions with the tile level[x,y] just like you were doing!
    }
}

Controlla l'esempio se hai ancora problemi.


Questo è un piccolo algoritmo molto carino, non avevo nemmeno sentito parlare dell'esempio Platformer (avrei dovuto, ma pretendo ignoranza). Grazie!
Ross,

@Ross Davvero? Saresti sorpreso di quanto sia simile la tua soluzione al campione. :-) Meno la parte dell'elenco, tutto il resto è praticamente identico (intersecare i riquadri di delimitazione, ottenere la profondità dell'intersezione, risolvere sull'asse più piccolo).
David Gouveia,

1
Oh amico, l'ho appena guardato. >. <Vorrei saperlo 2 giorni fa !! Bene, sono nuovo su XNA ma ho approfondito con la grafica 2D (solo OpenGL, non molta programmazione di gioco). Immagino che dovrei controllare più risorse prima di andare in testa al codice.
Ross,

1

Immagino che la mia risposta sarebbe la tua risposta! ;-)

Se hai la posizione (e le dimensioni) del giocatore puoi calcolare gli indici delle tessere circostanti (che sono le uniche da controllare in dettaglio). In questo modo dovrebbe essere irrilevante quanto sia grande la tua mappa, dipende solo dalle dimensioni effettive del tuo giocatore, risultando in più potenziali tessere da controllare.

Magari controlla il tutorial sulle collisioni su riemers.net se non l'hai già fatto.


Ho sentito parlare di Riemer ma non sono mai riuscito a cercare, grazie!
Ross,

1

Quando si ha a che fare con un gran numero di collisioni, di solito si desidera adottare una struttura più avanzata , come Quadtree o Hashmap per verificare tali collisioni.

Poiché le piastrelle sono statiche, suggerirei di utilizzare un Quadtree. Un albero quad è composto da quad. Ogni quadratino è composto da quattro rettangoli e ciascuno di questi rettangoli è quadruplo. Questo continua ricorsivamente fino a una dimensione specificata. Ogni quadratino può contenere un elenco di tessere che popolano quell'area dello schermo. In questo modo, quando si verificano le collisioni, è possibile

  1. Limitare i controlli a quelli nelle immediate vicinanze
  2. Limitare i controlli solo agli oggetti che si stanno muovendo

Ora se non vuoi nemmeno guardare le tessere fuori dallo schermo, allora potresti fare qualcosa del genere

public bool CheckCollision(myPosition) {
    if(quadNodes.Count > 0) {
        // This is not a leaf, keep checking
        foreach(Quad node in quadNodes) {
            if(node.Position is insideViewport && nearPlayer)
                // Continue recursion
            }
        }
    }
    else {
        // This is a leaf, do checks
        foreach(Tile tile in tileList) {
            if(collision)
                return true;
        }
        return false;
    }
}

Ho sentito parlare di Octrees nel rilevamento delle collisioni 3D, ma non ho mai visto una struttura dati avanzata utilizzata per il rilevamento delle collisioni 2D. Grazie mille!
Ross,

1
Dato che il suo gioco (supponendo Terraria) è fatto di tessere equidistanti, usare una griglia sarebbe sia molto più semplice che più veloce di un quadrifoglio. Un quadrifoglio funziona meglio per mondi più complessi in cui una griglia sarebbe difficile da adattare e ogni cosa ha dimensioni arbitrarie.
David Gouveia,

1
Hai ragione se si tratta di un gioco basato esclusivamente sulla griglia, anche per le dimensioni dei personaggi. So che in Terraria hanno anche mostri che non si adattano facilmente a un formato a griglia. Stavo correndo supponendo che il mondo di base fosse fatto di piastrelle, ma altri oggetti sarebbero diversi e avrebbero potuto immagazzinarli in una struttura simile per evitare di costruirne un altro. Suppongo che potrebbero usare una griglia per tessere, quindi una struttura diversa (se necessario) per altri oggetti arbitrari.
Mike Cluck,

1
Questo è quello che stavo per suggerire :) La griglia dovrebbe essere usata per gestire le collisioni con il terreno, mentre un quadrifoglio potrebbe essere usato per gestire le collisioni tra oggetti.
David Gouveia,

Vero. In questo momento ogni riquadro di delimitazione ha 2 ^ dimensioni di potenza. Questo rende molto più facile trovare il rilevamento delle collisioni. Una griglia si adatterebbe ai miei bisogni per ora.
Ross,
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.