Abbattimento efficiente di oggetti fuori schermo su una mappa 2D dall'alto verso il basso


8

So che l'efficienza è fondamentale nella programmazione del gioco e ho avuto alcune esperienze con il rendering di una "mappa" in precedenza, ma probabilmente non nel migliore dei modi.

Per un gioco 2D TopDown: (semplicemente renderizza le trame / tessere del mondo, nient'altro)

Supponiamo che tu abbia una mappa di 1000x1000 (tessere o altro). Se la tessera non è nella vista della telecamera, non dovrebbe essere renderizzata - è così semplice. Non è necessario eseguire il rendering di una tessera che non sarà visibile. Ma dal momento che hai oggetti 1000x1000 nella tua mappa, o forse meno, probabilmente non vorrai scorrere tutte le tessere 1000 * 1000 solo per vedere se si suppone che siano renderizzate o meno.

Domanda: Qual è il modo migliore per implementare questa efficienza? In modo che "rapidamente / più veloce" possa determinare quali tessere si suppone siano rese?

Inoltre, non sto costruendo il mio gioco attorno a tessere renderizzate con SpriteBatch, quindi non ci sono rettangoli, le forme possono avere dimensioni diverse e avere più punti, ad esempio un oggetto curvo di 10 punti e una trama all'interno di quella forma;

Domanda: Come si determina se questo tipo di oggetti è "all'interno" della vista della videocamera?

È facile con un rettangolo 48x48, basta vedere se X + Larghezza o Y + Altezza è nella vista della fotocamera. Diverso con più punti.

In poche parole, come gestire il codice e i dati in modo efficiente per non dover passare attraverso / scorrere un milione di oggetti contemporaneamente.

Risposte:


6

Per quanto riguarda gli oggetti complessi , penso che il modo migliore sia semplicemente ridurli ai rettangoli circostanti e verificare se quel rettangolo si trova all'interno della finestra. Anche se renderai una trama che non è effettivamente visibile (a causa della sua forma), sarà comunque probabilmente più veloce di fare un algoritmo di rilevamento più complesso.

Per quanto riguarda la gestione efficiente di mappe di grandi dimensioni, è necessario suddividere la mappa su una scala più ampia, ad esempio 10x10. Quindi controlli l'intersezione della finestra. Nel peggiore dei casi colpisce 4 questa 'regioni' che si tradurrà in (100x100) * 4 = 40K oggetti. Questo è un esempio semplificato. Per un uso reale dovresti prendere in considerazione la struttura Quadtree che è particolarmente efficiente per tali suddivisioni e rilevamento delle collisioni (il controllo della visibilità del viewport è fondamentalmente il controllo delle collisioni tra viewport e sprite).


L'uso di un Quadtree per le tessere mappa è un po 'eccessivo ... poiché puoi semplicemente calcolare gli indici corretti delle tessere che devono essere renderizzate. Inoltre, le regioni sono più semplici e consiglierei di usarle per il primo round di ottimizzazioni. Aiuterà a capire e usare i quadricipi in seguito :) +1.
Liosan,

@Liosan non è chiaro dalla domanda se queste 'piastrelle' abbiano le stesse dimensioni, altrimenti la soluzione sarebbe abbastanza banale, ovviamente.
Petr Abdulin,

hai ragione, Deukalion ha persino scritto un commento a una risposta diversa dicendo "E una tessera non ha sempre la stessa identica dimensione".
Liosan,

QuadTree funziona anche con dimensioni di area diverse da quelle esatte? Ogni regione non dovrebbe avere dimensioni rettangolari perché gli oggetti all'interno del rettangolo non lo sono, pertanto non crea un rettangolo. Quindi NON sarà il rendering di una griglia di 1024x1024 pixel, la sua forma potrebbe essere molto ortodossa.
Deukalion,

Beh, suppongo di no (non ho usato i quadrifici da solo), ma non importa se puoi mettere tutto nella regione rettangolare circostante. Ad ogni modo, se quadtree non è appropriato per il tuo compito per qualche motivo, molto probabilmente dovrai usare un approccio simile.
Petr Abdulin,

3

Quando hai molti oggetti mobili, dovresti memorizzarli in base alle loro coordinate in una struttura ad albero multidimensionale. In questo modo è possibile ottenere in modo efficiente un elenco di tutti gli oggetti che si trovano all'interno di un determinato rettangolo. Puoi anche ordinarli in base alle coordinate X o Y, il che è importante per l'ordine di disegno quando gli sprite degli oggetti si sovrappongono.

Questo sarà anche molto utile per il rilevamento delle collisioni.

Vedi l'articolo di Wikipedia sugli alberi kd per i dettagli.

Quando gli alberi 2D sono troppo complicati per te, c'è anche un'alternativa più semplice ma non molto meno efficace: conservare gli oggetti come figli delle tessere. Quando sposti un oggetto, lo rimuovi dall'elenco degli oggetti della sua vecchia tessera e lo metti nella lista degli oggetti di quello nuovo. Quando si disegnano gli oggetti, si scorre nuovamente le tessere nella finestra e si recuperano i loro oggetti. Quindi li ordina tutti per coordinate y e li disegna.


1

Non so se è il modo migliore, ma è così che imparo a farlo:

hai una matrice bidimensionale di "tessere"

public Tile tilemap[][];

e decidi la posizione della "macchina fotografica" con un Vector2, renderai solo ciò che è all'interno della scena, il grande rettangolo è ciò che puoi vedere sullo schermo, è inutile disegnare il resto della scena.

Ora devi ottenere gli offset, supponendo che tu voglia che la tua fotocamera sia al centro della scena:

offsetX = (graphics().width() / 2 - Math.round(cam.Position().X));
offsetX = Math.min(offsetX, 0);
offsetX = Math.max(offsetX, graphics().width() / 2 - mapWidth);
offsetY = (graphics().height()) / 2 - Math.round(cam.getPosition().Y);
offsetY = Math.min(offsetY, 0);
offsetY = Math.max((graphics().height() / 2 - mapHeight), offsetY);

ora, in quale parte dell'array iniziano e finiscono le tessere visibili?

firstTileX = pixelsToTiles(-offsetX);

lastTileX = firstTileX + pixelsToTiles(graphics().width());

firstTileY = pixelsToTiles(-offsetY);

lastTileY = firstTileY + pixelsToTiles(graphics().height());

int pixelsToTiles(int pixels) {
    return (int) Math.floor((float) pixels / Tile.getHeight());
}

e nel tuo metodo draw, esegui semplicemente il ciclo attraverso la parte visibile dell'array:

   for (int x = firstTileX; x < lastTileX; x++) {
        for (int y = firstTileY; y < lastTileY; y++) {
              Vector2 position = new Vector2(tilesToPixelsX(x) + offsetX,
                        tilesToPixelsY(y) + offsetY);
              tilemap[x][y].Draw(surf, position);
        }
    }

1
Sì, ma tile è stato un esempio per semplificare le cose. Ho già scritto che ho capito il processo per determinare se un oggetto è già in "vista" con forma rettangolare / titolo, non con forme più avanzate che hanno più punti. Inoltre, stavo cercando qualcosa che mi "non" passasse attraverso tutte le tessere durante ogni metodo Update () in XNA. Se ho una mappa "GRANDE", con circa 10000 oggetti (forme di 3 punti e più) non è questo il modo di farlo. Ogni aggiornamento devo eseguire un ciclo di 10000 aggiornamenti e altrettanti calcoli. Non uso tessere e questo non è efficiente; Ci sono stato.
Deukalion,

E una piastrella non è sempre della stessa identica dimensione, quindi non posso farlo nemmeno con quella. Non uso rettangoli.
Deukalion,

In parole povere: voglio solo passare in rassegna gli oggetti che DOVREBBERO essere resi, non fare il loop tra oggetti che non dovrebbero affatto.
Deukalion,

Il codice di @Deukalion Riktothepast FA solo scorrere le tessere che dovrebbero apparire all'interno del riquadro di delimitazione dello schermo (anche se questo non è molto chiaro). La stessa tecnica di base può essere utilizzata per eseguire il ciclo attraverso qualsiasi rettangolo di tessere all'interno di un determinato set di coordinate.
DampeS8N,

0

Puoi avere una Bitmap che è l'intera scena ma non visualizzata. E poi un Bitmap a livello di schermo della dimensione dello schermo visualizzato che disegna da tutta la scena ma solo la parte che deve essere mostrata.

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.