Generazione procedurale, aggiornamenti di gioco ed effetto farfalla


10

NOTA: l'ho chiesto su Stack Overflow alcuni giorni fa ma avevo pochissime visualizzazioni e nessuna risposta. Ho pensato che avrei dovuto chiedere invece su gamdev.stackexchange.

Questa è una domanda / richiesta generale per consigli su come mantenere un sistema di generazione procedurale attraverso più aggiornamenti post-release, senza interrompere i contenuti generati in precedenza.

Sto cercando di trovare informazioni e tecniche per evitare problemi di "effetto farfalla" durante la creazione di contenuti procedurali per i giochi. Quando si utilizza un generatore di numeri casuali con seeding, è possibile utilizzare una sequenza ripetuta di numeri casuali per creare un mondo riproducibile. Mentre alcuni giochi salvano semplicemente il mondo generato su disco una volta generati, una delle potenti funzionalità della generazione procedurale è il fatto che puoi fare affidamento sulla riproducibilità della sequenza numerica per ricreare una regione più volte allo stesso modo, eliminando la necessità di persistenza. A causa dei vincoli della mia situazione particolare, devo ridurre al minimo la persistenza e devo fare affidamento il più possibile sulla concentrazione puramente seminata.

Il principale pericolo in questo approccio è che anche il minimo cambiamento nel sistema di generazione procedurale può causare un effetto farfalla che cambia il mondo intero. Questo rende molto difficile aggiornare il gioco senza distruggere i mondi che i giocatori stanno esplorando.

La tecnica principale che ho usato per evitare questo problema è progettare la generazione procedurale in più fasi, ognuna delle quali ha il proprio generatore di numeri casuali con seeding. Ciò significa che ogni sottosistema è autonomo e se qualcosa si rompe non influirà su tutto il mondo. Tuttavia, sembra che abbia ancora un grande potenziale di "rottura", anche se in una parte isolata del gioco.

Un altro possibile modo di affrontare questo problema potrebbe essere mantenere versioni complete dei generatori all'interno del codice e continuare a utilizzare il generatore giusto per una determinata istanza mondiale. Questo mi sembra un incubo per la manutenzione e sono curioso di sapere se qualcuno lo fa davvero.

Quindi, la mia domanda è davvero una richiesta di consigli generali, tecniche e schemi di progettazione per affrontare questo problema dell'effetto farfalla, specialmente nel contesto degli aggiornamenti di gioco post-rilascio. (Spero non sia una domanda troppo ampia.)

Attualmente sto lavorando in Unity3D / C #, anche se questa è una domanda agnostica sul linguaggio.

AGGIORNARE:

Grazie per le risposte

Sembra sempre più che i dati statici siano l'approccio migliore e più sicuro, e inoltre che quando si memorizzano molti dati statici non è un'opzione, avere una lunga campagna in un mondo generato richiederebbe un rigoroso controllo delle versioni dei generatori utilizzati. Il motivo della limitazione nel mio caso è la necessità di un salvataggio / sincronizzazione del cloud basato su dispositivi mobili. La mia soluzione potrebbe essere quella di trovare modi per archiviare piccole quantità di dati compatti su cose essenziali.

Trovo che il concetto di "Gabbie" di Stormwind sia un modo particolarmente utile di pensare alle cose. Una gabbia è fondamentalmente un punto rialzato, prevenendo gli effetti di scorrimento di piccoli cambiamenti, cioè mettendo in gabbia la farfalla.


Sto votando per chiudere questa domanda come fuori tema perché è troppo ampia.
Almo,

Mi rendo conto che è molto ampio. Mi consiglieresti di provare un forum di Gamedev o qualcosa del genere? Non c'è davvero alcun modo per rendere la domanda più specifica. Speravo di poter sentire qualcuno con molta esperienza in questo settore, con alcuni trucchi astuti che non mi sono venuti in mente.
null

2
Almo si sbaglia. Non è affatto ampio. Questa è una domanda eccellente e abbastanza stretta da dare buone risposte. È qualcosa che penso che molti di noi procedurali abbiano spesso meditato.
Ingegnere

Risposte:


8

Penso che tu abbia coperto le basi qui:

  • Utilizzo di più generatori o re-seeding a intervalli (ad es. Utilizzo di hash spaziali) per limitare lo spillover dai cambiamenti. Questo probabilmente funziona per il contenuto cosmetico, ma come fai notare può comunque causare rotture contenute in una sezione.

  • Tenere traccia della versione del generatore utilizzata nel file di salvataggio e rispondere in modo appropriato. Ciò che significa "appropriato" potrebbe essere ...

    • Mantenere una cronologia di tutte le versioni precedenti dei generatori nel tuo gioco eseguibile e usare quella che corrisponde al salvataggio. Ciò rende più difficile correggere i bug se i giocatori continuano a usare vecchi salvataggi.
    • Avvertendo il lettore che questo file di salvataggio proviene da una versione precedente e fornisce un collegamento per accedere a quella versione come eseguibile separato. Buono per i giochi con campagne che durano da poche ore a giorni, cattivo per una campagna che ti aspetti di giocare per settimane o più.
    • Mantieni solo le ultime nversioni del generatore nel tuo eseguibile. Se il file di salvataggio utilizza una di quelle versioni recenti, (offrire di) aggiornare il file di salvataggio all'ultima versione. Questo utilizza il generatore appropriato per decomprimere qualsiasi stato obsoleto in letterali (o in delta dall'output del nuovo generatore sullo stesso seed, se sono molto simili). Qualsiasi nuovo stato da qui in poi proviene dai più recenti generatori. I giocatori che non giocano a lungo potrebbero essere lasciati indietro. E nel peggiore dei casi finisci per archiviare l'intero stato del gioco in forma letterale, nel qual caso potresti anche ...
  • Se si prevede di modificare frequentemente la logica di generazione e non si desidera interrompere la compatibilità con le versioni precedenti, non fare affidamento sul determinismo del generatore: salvare l'intero stato nel file di salvataggio. (vale a dire "Nuke dall'orbita. È l'unico modo per essere sicuri")


Se hai creato le regole di generazione, allora c'è un modo per invertire la generazione? IE, dato uno stato di gioco, puoi riportarlo a un seme? Se ciò è possibile con i tuoi dati, invece di collegare il giocatore a una versione di gioco diversa, potresti fornire un'utilità di aggiornamento che genera un mondo da un seme con il sistema precedente, quindi usa lo stato generato per produrre un seme per il nuovo generatore. Tuttavia, potresti dover avvisare i tuoi giocatori di attendere la conversione.
Joe,

1
Questo non è in generale possibile. Non hai nemmeno la garanzia che esista un seme per il nuovo generatore che fornisce lo stesso output di quello vecchio. Di solito questi semi contengono circa 64 bit, ma il numero di mondi possibili che il tuo gioco potrebbe supportare è probabilmente maggiore di 2 ^ 64, quindi ogni generatore ne produce solo un sottoinsieme. La modifica del generatore porterà molto probabilmente a un nuovo sottoinsieme di livelli, che potrebbe avere un'intersezione minima o addirittura nulla con il gruppo di generatori precedente.
DMGregory

È stato difficile scegliere la risposta "giusta". Ho scelto questo perché era conciso e sintetizzava in modo chiaro le questioni principali. Grazie.
null

4

La fonte primaria di tale effetto farfalla non è probabilmente la generazione di numeri - che dovrebbe essere abbastanza facile da mantenere deterministica da un singolo generatore di numeri - ma piuttosto l' uso di quei numeri dal codice client. Le modifiche al codice sono la vera sfida nel mantenere le cose stabili.

Codice: Test unitari Il modo migliore per garantire che qualche cambiamento minore da qualche parte non si manifesti involontariamente altrove, è includere test unitari approfonditi per ogni aspetto generativo, nella tua build. Questo vale per qualsiasi parte di codice compatto in cui il cambiamento di una cosa può avere un impatto su molte altre: sono necessari test per tutti in modo da poter vedere su un singolo build ciò che è stato influenzato.

Numeri: sequenze / slot periodici Supponiamo che tu abbia un generatore di numeri che serve a tutto. Non assegna significato, sputa semplicemente numeri in sequenza - come qualsiasi PRNG. Dato lo stesso seme in due sequenze, otteniamo le stesse sequenze, sì? Ora ci pensi e decidi che ci saranno forse 30 aspetti del tuo gioco che dovranno essere regolarmente forniti con un valore casuale. Qui assegniamo una sequenza ciclica di 30 slot, ad esempio ogni primo numero nella sequenza è layout di terreno accidentato, ogni secondo numero è perturbazioni del terreno ... ecc. ... ogni decimo numero aggiunge qualche errore allo stato AI per il realismo. Quindi il tuo ciclo è di 30.

Dopo 10, hai 20 slot liberi che puoi usare per altri aspetti man mano che la progettazione del gioco avanza. Il costo qui è ovviamente che è necessario generare numeri per gli slot 11-30 anche se non sono attualmente in uso , ovvero completare il periodo, per tornare alla sequenza successiva di 1-10. Questo ha un costo della CPU, anche se dovrebbe essere minore (a seconda del numero di slot liberi). L'altro aspetto negativo è che devi essere sicuro che il tuo progetto finale possa essere adattato al numero di slot che hai reso disponibile all'inizio del processo di sviluppo ... e più assegni all'inizio, più slot "vuoti" potenzialmente devi passare attraverso ciascuno, per far funzionare le cose.

Gli effetti di questo sono:

  • Hai un generatore che produce numeri per tutto
  • La modifica del numero di aspetti per i quali è necessario generare numeri, non influirà sul determinismo (a condizione che il periodo sia sufficientemente ampio da contenere tutti gli aspetti)

Naturalmente, ci sarà un lungo periodo durante il quale il tuo gioco non sarà disponibile al pubblico - in alpha, per così dire - in modo da poter ridurre da 30 a 20 aspetti senza influire su alcun giocatore, solo su te stesso, se ti rendessi conto che si era assegnato modo troppi slot in partenza. Ciò ovviamente salverebbe alcuni cicli della CPU. Ma tieni presente che una buona funzione di hash (che puoi scrivere da solo) dovrebbe essere velocissima, comunque. Quindi dover eseguire slot extra non dovrebbe essere costoso.


Ciao. Sembra simile ad alcune cose che sto facendo. Generalmente genera un gruppo di sotto-semi in anticipo sulla base del seme iniziale del mondo. Di recente ho iniziato a pre-generare un array longish di rumore, e quindi ogni "slot" è semplicemente un indice in quell'array. In questo modo ogni sottosistema può semplicemente prendere il seme giusto e lavorare in modo isolato. Un'altra grande tecnica è quella di utilizzare le coordinate x, y per generare un seme per ogni posizione. Sto usando il codice dalla risposta di Euforico su questa pagina dello stack: programmers.stackexchange.com/questions/161336/…
null

3

Se vuoi perseverare con PCG, ti suggerisco di trattare il codice PCG stesso come dati . Proprio come persisteresti i dati tra le revisioni con contenuto regolare, con il contenuto generato, se desideri persistere tra le revisioni, dovrai persistere nel generatore.

Naturalmente, l'approccio più popolare è quello di convertire i dati generati in dati statici, come hai già detto.

Non conosco esempi di giochi che mantengono in giro molte versioni di generatori, perché la persistenza è insolita nei giochi PCG - è per questo che il permadeath spesso va di pari passo con PCG. Tuttavia ci sono molti esempi di PCG multipli, anche dello stesso tipo, all'interno dello stesso gioco. Ad esempio, Unangband ha molti generatori separati per le stanze dei sotterranei e, man mano che ne vengono aggiunti di nuovi, quelli vecchi funzionano ancora allo stesso modo. Se questo è gestibile dipende dalla tua implementazione. Un modo per mantenerlo gestibile è utilizzare gli script per implementare i tuoi generatori, mantenendoli isolati con il resto del codice di gioco.


È un'idea intelligente, usare semplicemente generatori diversi per aree diverse.
null

2

Mantengo un'area di circa 30000 chilometri quadrati, con circa 1 milione di edifici e altri oggetti, oltre a posizionamenti casuali di cose varie. Una simulazione all'aperto ofc. I dati memorizzati sono di circa 4 GB. Sono fortunato ad avere spazio di archiviazione, ma non è illimitato.

Casuale è casuale, incontrollato. Ma si può ingabbiarlo un po ':

  • Controlla la fine dell'inizio (come menzionato in altri post, il numero di seed e il numero di numeri generati).
  • Limitare il suo spazio numerico, ad es. generare numeri interi solo tra 0 e 100.
  • Offset il suo spazio numerico, aggiungendo un valore (es. 100 + [numeri generati tra 0 e 100] produce numeri casuali tra 100 e 200)
  • Ridimensionalo (es. Moltiplica per 0,1)
  • E applica varie gabbie attorno. Questo sta riducendo, demolendo parte della generazione. Per esempio. se si genera in uno spazio bidimensionale, si può mettere un rettangolo sopra coppie di numeri e scartare ciò che è fuori. O un cerchio o un poligono. Se nello spazio 3D, si possono accettare ad esempio solo triplette che risiedono all'interno di una sfera o un'altra forma (ora pensare visivamente, ma questo non ha necessariamente a che fare con la visualizzazione o il posizionamento effettivi).

Questo è tutto. Anche le gabbie consumano dati purtroppo.

C'è un detto in finlandese, Hajota ja hallitse. Si traduce in Dividi e conquista .

Abbandonai rapidamente l'idea di una definizione precisa dei più piccoli dettagli. Il casuale vuole la libertà, quindi ha ottenuto la libertà. Lascia volare la farfalla - dentro la sua gabbia. Invece mi sono concentrato sull'avere un modo ricco di definire (e mainain !!) le gabbie. Non importa che macchine siano, purché siano blu o blu scuro (un datore di lavoro noioso ha detto una volta :-)). "Blu o blu scuro" è la gabbia (molto piccola) qui, lungo la dimensione del colore.

Cosa è gestibile, per controllare e gestire spazi numerici?

  • Una griglia booleana è (i bit sono piccoli!)
  • I punti d'angolo sono
  • Come è una struttura ad albero (= seguire in "gabbie che tengono gabbie")

Dal punto di vista della manutenzione e dell'intercompatibilità delle versioni ... abbiamo
: se version = n allora
: elseif version = m allora ...
Sì, la base di codice diventa più grande :-).

Cose familiari. Il modo corretto di procedere sarebbe quello di definire un metodo ricco per dividere e conquistare e sacrificare alcuni dati su questo. Quindi, ove possibile, concedi la libertà (locale) di randomizzazione, dove non è cruciale controllarla.

Non del tutto incompatibile con la divertente "nuke it fom orbit" proposta da DMGregory, ma forse usi bombe piccole e precise? :-)


Grazie per la tua risposta. Sembra un'area procedurale straordinariamente ampia da mantenere. Posso vedere come per un'area così ampia, anche quando si ha accesso a un sacco di spazio di archiviazione, è ancora impossibile semplicemente archiviare tutto. Sembra che i generatori con versione dovranno essere la strada da percorrere. Così sia :)
null

Di tutte le risposte, mi ritrovo a pensare di più a questo. Mi sono piaciute le tue descrizioni leggermente filosofiche delle cose. Trovo il termine "Gabbia" molto utile per spiegare le idee, quindi grazie per quello. Lascia volare la farfalla ... nella sua gabbia :)
null

PS Sono davvero curioso di sapere su quale gioco lavori. Sei in grado di condividere tali informazioni?
null

Potrebbe aggiungere un'altra cosa sullo spazio numerico: vale la pena rimanere sempre vicini allo zero. Che probabilmente già sapevi. Vicino a zero offre la migliore precisione numerica e la maggior parte del botto per meno bit. È sempre possibile compensare un intero gruppo di numeri vicini allo zero in un secondo momento, ma è necessario un solo numero per quello. Allo stesso modo, puoi (direi quasi che DEVI) spostare il calcolo distante più vicino a zero, con un offset. - Roccavento
Roccavento

Valutando il precedente, considera un piccolo movimento del veicolo, un incremento di 1 frame di 0,01 [metri, unità]: non puoi calcolare accuratamente 10000.1 + 0.01 con precisione numerica a 32 bit (singola) ma PUOI calcolare 0,1 + 0,01. Quindi, se l '"azione" si svolge molto lontano (dietro le montagne :-)), non andare lì, ma sposta le montagne verso di te (muovi con 10000, allora sei a 0.1 ora). Valido anche per lo spazio di archiviazione. Uno può essere avido con la memorizzazione di valori numerici vicini l'uno all'altro. Memorizza una volta la parte comune di esse e le variazioni singolarmente - possono salvare bit! Hai trovato il link? ;-)
Stormwind,
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.