Cosa si sta effettivamente muovendo in un corridore senza fine?


107

Ad esempio, il famoso gioco Flappy Bird, o qualsiasi altra cosa veramente ordinata, è il giocatore (in questo caso l'uccello, o la telecamera qualunque preferiate) che si muove in avanti o il mondo intero si muove all'indietro (l'uccello cambia solo posizione Y e ha un posizione X costante)?


1
Abbiamo eliminato molti commenti fuori tema da questa domanda e dalle sue risposte. Abbiamo anche spostato diverse discussioni ragionevoli per chattare, in alcuni casi ripetutamente. Nonostante ciò, diversi utenti hanno sentito la necessità di ignorare il fatto che i commenti non sono per discussioni estese e sono proseguiti. Pertanto, questa domanda è stata temporaneamente bloccata.
Josh

Risposte:


146

Non sono affatto d'accordo con la risposta di Philipp ; o almeno con come lo ha presentato. Dà l'impressione che spostare il mondo intorno al giocatore possa essere un'idea migliore; quando è l'esatto contrario. Quindi ecco la mia risposta ...


Entrambe le opzioni possono funzionare, ma in genere è una cattiva idea "invertire la fisica" spostando il mondo attorno al giocatore piuttosto che al giocatore in tutto il mondo.


Perdita / spreco di prestazioni:

Il mondo di solito avrà molti oggetti; molti se non la maggior parte, statici o addormentati. Il giocatore avrà uno o relativamente pochi oggetti. Spostare il mondo intero attorno al giocatore, significa spostare tutto nella scena tranne il giocatore. Oggetti statici, oggetti dinamici dormienti, oggetti dinamici attivi, sorgenti luminose, sorgenti sonore, ecc .; tutti devono essere spostati.

Questo è (ovviamente) considerevolmente più costoso che spostare solo ciò che si sta effettivamente muovendo (il giocatore, e forse qualche altra cosa).


Manutenibilità ed estensibilità:

Spostare il mondo intorno al giocatore fa sì che il mondo (e tutto ciò che contiene) sia il punto in cui le cose accadono più attivamente. Qualsiasi bug o cambiamento nel sistema significa che, potenzialmente, tutto cambia. Questo non è un buon modo di fare le cose; vuoi che i bug / le modifiche siano il più isolati possibile, in modo da non avere comportamenti imprevisti da qualche parte in cui non hai apportato modifiche.

Ci sono anche molti altri problemi con questo approccio. Ad esempio, infrange molte ipotesi su come le cose dovrebbero funzionare all'interno del motore. Non saresti in grado di usare la dinamica RigidBodyper qualcosa di diverso dal giocatore, per esempio; come un oggetto con un allegato RigidBodynon impostato su cinematica si comporterà inaspettatamente quando si imposta posizione / rotazione / scala (cosa che si farebbe in ogni fotogramma, per ogni oggetto nella scena, tranne il giocatore 😨)


Quindi la risposta è spostare il giocatore solo allora!

Bene ... e no . Come menzionato nella risposta di Philipp, in un tipo di gioco a corridore infinito (o qualsiasi gioco con una grande area esplorabile senza soluzione di continuità), andare troppo lontano dall'origine introdurrebbe alla fine degli FPPE evidenti ( errori di precisione a virgola mobile ) e, ancora, infine, straripare il tipo numerico, causando l'arresto anomalo del gioco o, fondamentalmente, facendo scoppiare il fumo del mondo di gioco ... Sugli steroidi! 😵 (perché a questo punto, gli FPPE renderebbero il gioco già in crack "normale")


La vera soluzione:

Non fare nessuno dei due e fare entrambi! Dovresti mantenere il mondo statico e muoverlo attorno. Ma "ri-root" sia il giocatore che il mondo, quando il giocatore inizia ad allontanarsi troppo dalla radice (posizione [0, 0, 0]) della scena.

Se mantieni la posizione relativa delle cose (giocatore rispetto al mondo che lo circonda), e fai questo processo in un singolo aggiornamento di fotogrammi, il giocatore (effettivo) non se ne accorgerà nemmeno!

Per farlo, hai due opzioni principali:

  1. Sposta il giocatore nella radice della scena e sposta il blocco del mondo nella sua nuova posizione rispetto al giocatore.
  2. Pensa al mondo come una griglia; sposta la parte della griglia in cui si trova il giocatore nella radice e sposta il giocatore nella sua nuova posizione rispetto a quella parte della griglia.

Ecco un esempio di questo processo in azione


Ma quanto è troppo lontano?

Se guardi il codice sorgente di Unity, usano 1e-5( 0.00001) come base per considerare due valori in virgola mobile "uguali", dentro Vector2e Vector3(i tipi di dati responsabili delle posizioni degli oggetti, [eulero-] rotazioni e scale). Poiché la perdita di precisione in virgola mobile avviene in entrambi i modi a partire da zero, è lecito ritenere che qualsiasi cosa sotto 1e+5( 100000) unità lontana dalla radice / origine della scena sia sicuro da lavorare.

Ma! Da...

  1. È più appropriato creare un sistema per gestire automaticamente questi processi di re-rooting.
  2. Qualunque sia il tuo gioco, non è necessario che una "sezione" contigua del mondo sia larga 100000 unità (metri [?]).

... quindi è probabilmente una buona idea fare il rooting molto prima / più spesso di quel marchio di 100000 unità. Il video di esempio che ho fornito sembra farlo ogni 1000 unità circa, per esempio.


2
I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat . Si prega di utilizzare quella chat invece di pubblicare più commenti qui.
Alexandre Vaillancourt

> Questo è (ovviamente) considerevolmente più costoso che spostare solo ciò che si sta effettivamente muovendo <È vero? Durante il rendering, dovrete comunque fare una moltiplicazione delle matrici con la matrice della telecamera su tutti gli oggetti, avere la telecamera fissata sull'identità sarebbe meno costoso in questo modo.
Matsemann,

Quel momento in cui lo sviluppo del gioco diventa sviluppo dell'unità ...
LJ ᛃ

@Matsemann - Se fossi andato a chattare, avresti notato che questo non è un punto nuovo. Come già spiegato, rendere la scena NOT-EQUALS; sono soggetti e condutture differenti e quasi completamente indipendenti. Fare un'inversione del frame di riferimento nel modo in cui gli oggetti sono posizionati nella scena significa che avrai un processo più pesante su entrambe le estremità , piuttosto che solo nel rendering. --- Inoltre, anche se la performance fosse equamente scambiata come dici tu, tutti gli altri problemi con l'inversione rimarrebbero ancora irrisolti, rendendo questo approccio ancora peggiore di quello del re-rooting.
XenoRo l'

@LJ ᛃ La domanda è stata fatta specificamente sull'unità (vedere i tag di OP prima delle modifiche di D. Everhard [IMHO defacing]), e quindi la risposta è stata adattata per riflettere questo . --- Ma ecco la parte migliore: Be it Unity, Unreal, CryEngine, Source, ecc ... I motori migliori e / o più popolari funzionano in questo modo (e lo fanno per un motivo), quindi la risposta non è solo perfettamente valido in generale, ma i punti sollevati sono ancora all'apice della precisione . In generale, se inverti il ​​quadro di riferimento della fisica, puoi aspettarti di incorrere in problemi, specialmente su oggetti dinamici.
XenoRo l'

89

Entrambe le opzioni funzionano.

Ma se vuoi che il corridore senza fine sia davvero infinito, dovrai mantenere il giocatore fermo e muovere il mondo. Altrimenti alla fine colpirai i limiti delle variabili che usi per memorizzare la posizione X. Un numero intero alla fine traboccerebbe e una variabile in virgola mobile diventerebbe sempre meno accurata, il che renderebbe il gioco glitch dopo un po '. Ma puoi evitare questo problema usando un tipo abbastanza grande che nessuno incontrerà questi problemi entro un lasso di tempo che si potrebbe concepire in una sessione (quando un giocatore si sposta di 1000 pixel al secondo, un numero intero di 32 bit traboccerà dopo 49 giorni).

Quindi fai quello che ti sembra concettualmente più intuitivo.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Josh

1
Non penso che risolva nulla. Invece di mantenere la posizione del giocatore (e della fotocamera), mantieni la posizione di scorrimento del mondo - in che modo è diverso?
Agent_L

1
@Agent_L Generalmente, i mondi vengono generati frammentariamente e ogni pezzo ha la sua posizione X / Y. Quando il pezzo si lascia alle spalle del giocatore abbastanza (ad es. Fuori schermo), viene eliminato e viene generato solo un certo importo in anticipo, quindi sono sempre all'interno di un intervallo relativamente piccolo - un gioco che ho realizzato non ha mai avuto una coordinata maggiore di ~ 1000 o meno di 0, nonostante sia un corridore infinito, generato casualmente, per questo - ho tenuto traccia della posizione del giocatore in ciascun blocco e dell'indice di ciascun blocco nella sequenza.
Nic Hartley,

Non compro il concetto di "overflow variabile". Se il giocatore non si muove, lo "sfondo" sarà compensato negativamente della stessa quantità che il giocatore verrebbe compensato se si stesse muovendo. Entrambi i casi incontrerebbero esattamente lo stesso problema (in direzioni opposte) ed entrambi richiederebbero una gestione speciale ai limiti del trabocco.
spender il

2
a 1000 pixel al secondo occorrerebbero oltre 586 milioni di anni per superare un numero intero a 64 bit. Questo è davvero un problema solo se stai usando tipi molto piccoli come numeri interi a 16 bit.
Lyndon White l'

38

Sulla base della risposta di XenoRo , invece del metodo di re-rooting che descrivono, si potrebbe fare quanto segue:

Crea un buffer circolare di parti della tua mappa infinita generate, attraverso le quali il tuo personaggio si sposta con la posizione aggiornata con l'aritmetica del modulo (quindi corri semplicemente attorno al buffer circolare). Inizia a sostituire parti del buffer non appena il tuo personaggio lascia il blocco. L'equazione di aggiornamento dei giocatori sarebbe simile a:

player.position = (player.position + player.velocity) % worldBuffer.width;

ecco un esempio pittorico di ciò di cui sto parlando:

inserisci qui la descrizione dell'immagine

Ecco un esempio di ciò che accade alla fine.

inserisci qui la descrizione dell'immagine

Con questo metodo non si verificano mai errori di precisione, ma potrebbe essere necessario creare un buffer molto grande se si intende farlo in 3d con una distanza di visione molto lontana (poiché sarà comunque necessario essere in grado di vedere davanti a sé ). Se il suo uccello floscio la dimensione del tuo pezzo sarà probabilmente solo grande quanto deve essere per contenere una singola scena per un singolo schermo e il buffer può essere molto piccolo.

Si noti che inizierai a ottenere risultati ripetitivi con QUALSIASI prng e il numero massimo di sequenze non ripetitive di una generazione PRNG è in genere inferiore alla lunghezza di pow (2, numero di bit utilizzati internamente), con merzenne twister questo non è gran parte di un problema, poiché utilizza 2,5k di stato interno, ma se si utilizza la variante minuscola, si hanno 2 ^ 127-1 iterazioni massime prima di ripetere (o peggio), questo è comunque un numero astronomicamente elevato . Puoi risolvere i problemi di ripetizione del periodo anche se il tuo PRNG ha un breve periodo tramite buone funzioni di miscelazione valanghe per il seme (quando aggiungi più stato implicitamente) ripetutamente.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Josh

15

Come già chiesto e accettato, dipende davvero dalla portata e dallo stile del tuo gioco, ma poiché non è stato menzionato: FlappyBird sposta l'ostacolo attraverso lo schermo, piuttosto che il giocatore in tutto il mondo.

Un spawner sta istanziando oggetti fuori dallo schermo con una velocità fissa nella Vector2.leftdirezione.


3
Funziona con FlappyBird perché è un gioco molto semplice. Come menzionato nella mia risposta, la stessa tecnica, sebbene ancora possibile , sarebbe molto problematica su qualcosa di più complesso (con più oggetti / dettagli del mondo), come Subway Surfer.
XenoRo,

15
Sono d'accordo e la tua risposta è molto approfondita. Non ho visto nessuno menzionare quale metodo effettivamente utilizza l'uccello di sbattimento, quindi ho pensato di aggiungerlo.
Stephan,
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.