Meccanismo di inversione temporale nei giochi


10

Mi chiedo come siano tipicamente progettati i meccanismi di manipolazione del tempo nei giochi. Sono particolarmente interessato all'inversione del tempo (un po 'come nell'ultimo SSX o Prince of Persia).

Il gioco è uno sparatutto top down 2D.

Il meccanismo che sto cercando di progettare / implementare ha i seguenti requisiti:

1) Le azioni di entità diverse dal personaggio del giocatore sono completamente deterministiche.

  • L'azione intrapresa da un'entità si basa sui frame fatti dall'inizio del livello e / o sulla posizione del giocatore sullo schermo
  • Le entità vengono generate all'ora prestabilita durante il livello.

2) Il reverse time funziona invertendo in tempo reale.

  • Anche le azioni del giocatore sono invertite, ripete al contrario ciò che il giocatore ha eseguito. Il giocatore non ha controllo durante il tempo inverso.
  • Non vi è alcun limite al tempo trascorso all'inversione, se lo si desidera è possibile invertire completamente l'inizio del livello.

Come esempio:

Fotogrammi 0-50: il giocatore muove in avanti di 20 unità in questo momento Il nemico 1 si genera nel frame 20 Il nemico 1 si sposta di 10 unità nel frame 30-40 Il giocatore spara proiettile nel frame 45 Il proiettile viaggia 5 in avanti (45-50) e uccide il nemico 1 in telaio 50

Inversione di questo sarebbe riprodotta in tempo reale: il giocatore si sposta indietro di 20 unità durante questo periodo. Il nemico 1 rianima al frame 50 Il proiettile riappare al frame 50 Il proiettile si sposta indietro 5 e scompare (50-45) Il nemico si sposta a sinistra 10 (40-30) Il nemico rimosso a telaio 20.

Solo guardando il movimento avevo alcune idee su come raggiungere questo obiettivo, ho pensato di avere un'interfaccia che ha cambiato il comportamento per quando il tempo stava avanzando o invertendo. Invece di fare qualcosa del genere:

void update()
{
    movement += new Vector(0,5);
}

Vorrei fare qualcosa del genere:

public interface movement()
{
    public void move(Vector v, Entity e);
}

public class advance() implements movement
{
    public void move(Vector v, Entity e)
    {
            e.location += v;
    }
}


public class reverse() implements movement
{
    public void move(Vector v, Entity e)
    { 
        e.location -= v;
    }
}

public void update()
{
    moveLogic.move(new vector(5,0));
}

Tuttavia mi sono reso conto che questo non sarebbe stato ottimale per le prestazioni e sarebbe diventato rapidamente complicato per azioni più avanzate (come movimenti fluidi lungo percorsi curvi ecc.).


1
Non ho visto tutto questo (YouTube shaky cam, 1,5 ore) , ma forse ci sono alcune idee di Jonathan Blow che ha funzionato nel suo gioco Braid.
MichaelHouse

Risposte:


9

Potresti dare un'occhiata al modello di comando .

Fondamentalmente ogni azione reversibile intrapresa dalle tue entità viene implementata come oggetto comando. Tutti questi oggetti implementano almeno 2 metodi: Execute () e Undo (), oltre a qualsiasi altra cosa di cui abbiate bisogno, come una proprietà di timestamp per un tempismo corretto.

Ogni volta che la tua entità esegue un'azione reversibile, devi prima creare un oggetto comando appropriato. Lo salvi su uno stack Annulla, quindi inserisci il tuo motore di gioco ed eseguilo. Quando vuoi invertire il tempo, fai scoppiare le azioni dall'alto dello stack e chiami il loro metodo Undo (), che fa l'opposto del metodo Execute (). Ad esempio, in caso di un salto dal punto A al punto B, si esegue un salto da B ad A.

Dopo aver fatto scattare un'azione, salvala su uno stack Ripeti se vuoi andare avanti e indietro a piacimento, proprio come la funzione annulla / ripeti in un editor di testo o in un programma di disegno. Naturalmente, anche le tue animazioni devono supportare una modalità di "riavvolgimento" per riprodurle all'indietro.

Per ulteriori shenanigans di progettazione del gioco, lascia che ogni entità memorizzi le proprie azioni sul proprio stack, in modo da poterle annullare / ripetere indipendentemente l'una dall'altra.

Un modello di comando presenta altri vantaggi: ad esempio, è praticamente banale costruire un registratore replay, dal momento che è sufficiente salvare tutti gli oggetti sugli stack in un file e, al momento della riproduzione, inserirlo nel motore di gioco uno per uno.


2
Nota che la reversibilità delle azioni in un gioco può essere molto delicata, a causa di problemi di precisione in virgola mobile, tempistiche variabili, ecc .; è molto più sicuro salvare quello stato che ricostruirlo nella maggior parte delle situazioni.
Steven Stadnicki,

@StevenStadnicki Forse, ma è sicuramente possibile. In cima alla mia testa, C&C Generals lo fa in questo modo. Ha replay di ore fino a 8 giocatori, con un peso di qualche centinaio di KB al peggio, ed è il modo in cui immagino di più se non tutti i giochi RTS fanno il loro multiplayer: non puoi semplicemente trasmettere lo stato di gioco completo con potenzialmente centinaia di unità per ogni frame, devi consentire al motore di eseguire l'aggiornamento. Quindi sì, è sicuramente praticabile.
Hackworth,

3
La riproduzione è una cosa molto diversa dal riavvolgimento, poiché le operazioni che sono costantemente riproducibili in avanti (ad esempio, trovare la posizione nel frame n, x_n, iniziando con x_0 = 0 e aggiungendo i delta v_n per ogni passaggio) non sono necessariamente riproducibili all'indietro ; (x + v_n) -v_n non è costantemente uguale a x in matematica a virgola mobile. Ed è facile dire "aggirarlo", ma si sta parlando potenzialmente di una revisione completa, incluso il fatto di non poter utilizzare molte librerie esterne.
Steven Stadnicki,

1
Per alcuni giochi il tuo approccio potrebbe essere fattibile, ma la maggior parte dei giochi AFAIK che usano l'inversione del tempo come meccanico stanno usando qualcosa di più vicino all'approccio Memento di OriginalDaemon in cui lo stato rilevante viene salvato per ogni fotogramma.
Steven Stadnicki,

2
Che dire del riavvolgimento ricalcolando i passaggi, ma salvando un fotogramma chiave ogni paio di secondi? È probabile che gli errori in virgola mobile non facciano una differenza significativa in pochi secondi (ovviamente in base alla complessità). Ha anche dimostrato di funzionare nella compressione video: P
Tharwen,

1

Potresti dare un'occhiata al modello Memento; la sua intenzione principale è quella di implementare le operazioni di annullamento / ripetizione eseguendo il rollback dello stato dell'oggetto, ma per alcuni tipi di giochi dovrebbe essere sufficiente.

Per un gioco in un ciclo in tempo reale potresti considerare ogni fotogramma delle tue operazioni come un cambio di stato e memorizzarlo. Questo è un approccio semplice da implementare. L'alternativa è intercettare quando lo stato di un oggetto viene modificato. Ad esempio, rilevare quando vengono cambiate le forze che agiscono su un corpo rigido. Se stai usando le proprietà per ottenere e impostare le variabili, questa può anche essere un'implementazione relativamente semplice, la parte difficile è identificare quando ripristinare lo stato, poiché non sarà lo stesso tempo per ogni oggetto (potresti archiviare il tempo di rollback come conteggio dei frame dall'inizio del sistema).


0

Nel tuo caso particolare, la gestione del rollback riavvolgendo il movimento dovrebbe funzionare correttamente. Se stai usando qualsiasi forma di pathfinding con le unità AI, assicurati di ricalcolarlo dopo il rollback per evitare la sovrapposizione di unità.

Il problema è il modo in cui gestisci il movimento stesso: un decente motore fisico (uno sparatutto top down 2D andrà bene con uno molto semplice) che tiene traccia delle informazioni sui passi precedenti (tra cui posizione, forza netta, ecc.) Fornirà una base solida. Quindi, decidendo un rollback massimo e una granularità per i passaggi di rollback, dovresti ottenere il risultato desiderato.


0

Mentre questa è un'idea interessante. Non lo consiglierei.

La riproduzione in avanti del gioco funziona bene, poiché un'operazione avrà sempre lo stesso effetto sullo stato del gioco. Ciò non implica che l'operazione inversa fornisca lo stato originale. Ad esempio, valuta la seguente espressione in qualsiasi linguaggio di programmazione (disattiva l'ottimizzazione)

(1.1 + 3 - 3) == 1.1

Almeno in C e C ++, restituisce false. Mentre la differenza può essere piccola, immagina quanti errori possono accumularsi a 60 fps in 10 secondi di minuti. Ci saranno casi in cui un giocatore perde solo qualcosa, ma lo colpisce mentre il gioco viene riprodotto all'indietro.

Consiglierei di memorizzare i fotogrammi chiave ogni mezzo secondo. Questo non occuperà troppa memoria. È quindi possibile interpolare tra i fotogrammi chiave o, ancora meglio, simulare il tempo tra due fotogrammi chiave, quindi riprodurlo all'indietro.

Se il tuo gioco non è troppo complicato, archivia solo i fotogrammi chiave dello stato del gioco 30 volte al secondo e gioca all'indietro. Se avessi 15 oggetti ciascuno con una posizione 2D, occorrerebbero 1,5 minuti per arrivare a un MB, senza compressione. I computer hanno gigabyte di memoria.

Quindi non complicarti troppo, non sarà facile riprodurre un gioco all'indietro e causerà molti errori.

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.