Come costruire un sistema Time-Rewind Prince-of-Persia Style, parte 2

Cosa starai creando

L'ultima volta abbiamo creato un semplice gioco in cui è possibile riavvolgere il tempo fino a un punto precedente. Ora consolideremo questa funzionalità e renderla molto più divertente da usare.

Tutto ciò che facciamo qui si baserà sulla parte precedente, quindi dai un'occhiata! Come prima, avrai bisogno dell'Unità e di una comprensione di base di esso.

Pronto? Andiamo!

Registra meno dati e interpola

In questo momento registriamo le posizioni e le rotazioni del giocatore 50 volte al secondo. Questa quantità di dati diventerà rapidamente insostenibile, e questo diventerà particolarmente evidente con configurazioni di gioco più complesse e dispositivi mobili con meno potenza di elaborazione.

Ma ciò che possiamo fare invece è solo registrare 4 volte al secondo e interpolare tra quei keyframe. In questo modo risparmiamo il 92% dell'ampiezza di banda di elaborazione e otteniamo risultati che non sono distinguibili dalle registrazioni a 50 fotogrammi, poiché si riproducono entro frazioni di secondo.

Inizieremo registrando solo un fotogramma chiave ogni x fotogrammi. Per fare ciò, abbiamo prima bisogno di queste nuove variabili:

public int keyframe = 5; private int frameCounter = 0;

La variabile fotogramma chiave è la cornice in FixedUpdate metodo con cui registreremo i dati del giocatore. Attualmente è impostato su 5, che significa ogni cinque volte il FixedUpdate il metodo scorre, i dati verranno registrati. Come FixedUpdate funziona 50 volte al secondo, questo significa che verranno registrati 10 fotogrammi al secondo, rispetto ai 50 precedenti. La variabile frameCounter sarà usato per contare i frame fino al prossimo keyframe.

Ora adattare il blocco di registrazione nel FixedUpdate funzione per assomigliare a questo:

if (! isReversing) if (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);  

Se provi ora, vedrai che il riavvolgimento prende parte a un tempo molto più breve rispetto a prima. Questo perché abbiamo registrato meno dati, ma li abbiamo riprodotti a velocità normale. Ora dobbiamo cambiarlo.

Innanzitutto, ne abbiamo bisogno di un altro frameCounter variabile per tenere traccia non della registrazione dei dati, ma di riprodurli.

private int reverseCounter = 0;

Adatta il codice che ripristina la posizione del giocatore per utilizzarlo allo stesso modo in cui registriamo i dati. Il FixedUpdate la funzione dovrebbe quindi assomigliare a questa:

void FixedUpdate () if (! isReversing) if (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);   else  if(reverseCounter > 0) reverseCounter - = 1;  else player.transform.position = (Vector3) playerPositions [playerPositions.Count - 1]; playerPositions.RemoveAt (playerPositions.Count - 1); player.transform.localEulerAngles = (Vector3) playerRotations [playerRotations.Count - 1]; playerRotations.RemoveAt (playerRotations.Count - 1); reverseCounter = keyframe; 

Quando riavvolgi il tempo ora, il giocatore tornerà alle posizioni precedenti, in tempo reale!

Non è proprio quello che vogliamo, però. Abbiamo bisogno di interpolare tra quei keyframe, che saranno un po 'più complicati. Innanzitutto, abbiamo bisogno di queste quattro variabili:

privato Vector3 currentPosition; private Vector3 previousPosition; private Vector3 currentRotation; private Vector3 previousRotation;

Quelli salveranno i dati del giocatore corrente e quello dal fotogramma chiave registrato prima di quello in modo che possiamo interpolare tra questi due.

Quindi abbiamo bisogno di questa funzione:

void RestorePositions () int lastIndex = keyframes.Count - 1; int secondToLastIndex = keyframes.Count - 2; if (secondToLastIndex> = 0) currentPosition = (Vector3) playerPositions [lastIndex]; previousPosition = (Vector3) playerPositions [secondToLastIndex]; playerPositions.RemoveAt (lastIndex); currentRotation = (Vector3) playerRotations [lastIndex]; previousRotation = (Vector3) playerRotations [secondToLastIndex]; playerRotations.RemoveAt (lastIndex); 

Questo assegnerà le informazioni corrispondenti alle variabili di posizione e rotazione che interpolaremo tra. Ne abbiamo bisogno in una funzione separata, come la chiamiamo in due punti diversi.

Il nostro blocco di ripristino dei dati dovrebbe assomigliare a questo:

if (reverseCounter> 0) reverseCounter - = 1;  else reverseCounter = keyframe; RestorePositions ();  if (firstRun) firstRun = false; RestorePositions ();  float interpolation = (float) reverseCounter / (float) keyframe; player.transform.position = Vector3.Lerp (previousPosition, currentPosition, interpolation); player.transform.localEulerAngles = Vector3.Lerp (previousRotation, currentRotation, interpolation);

Chiamiamo la funzione per ottenere l'ultimo e il secondo all'ultimo set di informazioni dai nostri array ogni volta che il contatore raggiunge l'intervallo di fotogrammi chiave che abbiamo impostato (nel nostro caso 5), ma dobbiamo anche chiamarlo sul primo ciclo quando sta avvenendo il ripristino. Questo è il motivo per cui abbiamo questo blocco:

if (firstRun) firstRun = false; RestorePositions (); 

Affinché questo funzioni, è necessario anche il prima corsa variabile:

private bool firstRun = true;

E per resettarlo quando il pulsante dello spazio è sollevato:

if (Input.GetKey (KeyCode.Space)) isReversing = true;  else isReversing = false; firstRun = true; 

Ecco come funziona l'interpolazione:

Invece di usare solo l'ultimo fotogramma chiave che abbiamo salvato, questo sistema ottiene l'ultimo e il penultimo e interpola tra loro. La quantità di interpolazione si basa sulla distanza tra i frame attualmente. 

Tutto ciò avviene tramite la funzione Lerp, dove aggiungiamo la posizione corrente (o rotazione) e quella precedente. Quindi viene calcolata la frazione dell'interpolazione, da cui può passare 0 a 1. Quindi il giocatore si trova nella posizione equivalente tra i due punti salvati, ad esempio il 40% sulla rotta fino all'ultimo fotogramma chiave.

Quando lo rallentate e lo riproducete fotogramma per fotogramma, potete effettivamente vedere lo spostamento del personaggio giocatore tra quei fotogrammi chiave, ma nel gameplay non è evidente.

E così abbiamo notevolmente ridotto la complessità della configurazione di riavvolgimento temporale e reso molto più stabile.

Registra solo un numero fisso di fotogrammi chiave

Ora che abbiamo ridotto notevolmente il numero di fotogrammi che effettivamente risparmiamo, possiamo assicurarci di non salvare troppi dati.

In questo momento abbiamo solo messo i dati registrati nell'array, che non funzionerà a lungo termine. Man mano che l'array cresce, diventerà più ingombrante, l'accesso richiederà più tempo e l'intera configurazione diventerà più instabile.

Per risolvere questo problema, possiamo istituire un codice che controlli se l'array è cresciuto oltre una certa dimensione. Se sappiamo quanti fotogrammi al secondo risparmiamo, possiamo determinare quanti secondi di tempo di riavvolgimento dovremmo salvare e cosa sarebbe adatto al nostro gioco e alla sua complessità. Il piuttosto complesso principe di Persia consente forse 15 secondi di tempo riavvolgibile, mentre la configurazione più semplice di Treccia consente il riavvolgimento illimitato.

if (playerPositions.Count> 128) playerPositions.RemoveAt (0); playerRotations.RemoveAt (0); 

Quello che succede è che una volta che la matrice cresce di una certa dimensione, rimuoviamo la prima voce di essa. Quindi rimane solo fino a quando vogliamo che il giocatore si riavvolga, e non c'è pericolo che diventi troppo grande per essere usato in modo efficiente. Metti questo nel FixedUpdate funzione dopo il codice di registrazione e riproduzione.

Utilizzare una classe personalizzata per tenere i dati del giocatore

In questo momento registriamo le posizioni dei giocatori e le rotazioni in due matrici separate. Mentre funziona, dobbiamo ricordarci di registrare e accedere sempre ai dati in due posti contemporaneamente, il che ha il potenziale per problemi futuri.

Quello che possiamo fare, comunque. è creare una classe separata per contenere entrambe queste cose e possibilmente anche di più (se ciò dovesse essere necessario nel progetto).

Il codice per una classe personalizzata che funge da contenitore per i dati è simile al seguente:

Keyframe di classe pubblica posizione pubblica Vector3; rotazione pubblica di Vector3; Keyframe pubblico (posizione Vector3, rotazione Vector3) this.position = position; this.rotation = rotation; 

È possibile aggiungerlo al file TimeController.cs, subito prima dell'avvio della dichiarazione della classe. Quello che fa è fornire un contenitore per salvare sia la posizione che la rotazione del giocatore. Il metodo del costruttore consente di essere creato direttamente con le informazioni necessarie.

Il resto dell'algoritmo dovrà essere adattato per funzionare con il nuovo sistema. Nel metodo Start, l'array deve essere inizializzato:

keyframes = new ArrayList ();

E invece di dire:

playerPositions.Add (player.transform.position); playerRotations.Add (player.transform.localEulerAngles);

Possiamo salvarlo direttamente in un oggetto Keyframe:

keyframes.Add (nuovo Keyframe (player.transform.position, player.transform.localEulerAngles));

Quello che facciamo qui è aggiungere la posizione e la rotazione del giocatore nello stesso oggetto, che poi viene aggiunto in un singolo array, il che riduce notevolmente la complessità di questa configurazione.

Aggiungi un effetto sfocato per indicare che il riavvolgimento sta avvenendo

Abbiamo drasticamente bisogno di una sorta di significante che ci dice che il gioco è attualmente in fase di riavvolgimento. Proprio adesso, noi lo sai, ma un giocatore potrebbe essere confuso. In tali situazioni è bene avere più cose che dicono al giocatore che il riavvolgimento sta accadendo, come visivamente (attraverso l'intero schermo che si sfoca un po ') e audio (dalla musica che rallenta e si inverte).

Facciamo qualcosa di simile a come principe di Persia lo fa e aggiunge un po 'di sfocatura.

Tempo di riavvolgimento da Prince of Persia: The Forgotten Sands

Unity ti consente di aggiungere più effetti di una fotocamera l'uno sull'altro e con alcuni esperimenti puoi crearne uno che si adatti perfettamente al tuo progetto.

Prima di poter utilizzare gli effetti di base, dobbiamo importarli. Per fare questo, andare a Risorse> Importa pacchetto> Effetti, e importa tutto ciò che ti viene offerto.

Gli effetti visivi possono essere aggiunti direttamente alla fotocamera principale. Vai a Componenti> Effetti immagine e aggiungere un blur e a fioritura effetto. La combinazione di questi due dovrebbe fornire un buon effetto per quello che stiamo andando.

Queste sono le impostazioni di base. Puoi regolarli per adattarli meglio al tuo progetto.

Quando lo provi ora, il gioco avrà questo effetto tutto il tempo.

Ora dobbiamo attivarlo e disattivarlo rispettivamente. Per quello, il TimeController ha bisogno di importare gli effetti dell'immagine. Aggiungi questa linea fin dall'inizio:

utilizzando UnityStandardAssets.ImageEffects;

Per accedere alla videocamera dal TimeController, aggiungi questa variabile:

macchina fotografica privata;

E assegnarlo nel Inizio funzione:

camera = Camera.main;

Quindi aggiungi questo codice per attivare gli effetti durante il riavvolgimento del tempo e attivali in caso contrario:

void Update () if (Input.GetKey (KeyCode.Space)) isReversing = true; camera.GetComponent() .enabled = true; camera.GetComponent() .enabled = true;  else isReversing = false; firstRun = true; camera.GetComponent() .enabled = false; camera.GetComponent() .enabled = false; 

Quando premi il pulsante dello spazio, ora non solo riavvolgi la scena, ma attivi anche l'effetto di riavvolgimento sulla fotocamera, dicendo al giocatore che sta succedendo qualcosa.

L'intero codice del TimeController dovrebbe assomigliare a questo:

usando UnityEngine; usando System.Collections; utilizzando UnityStandardAssets.ImageEffects; Keyframe di classe pubblica posizione pubblica Vector3; rotazione pubblica di Vector3; Keyframe pubblico (posizione Vector3, rotazione Vector3) this.position = position; this.rotation = rotation;  TimeController di classe pubblica: MonoBehaviour public GameObject player; fotogrammi chiave ArrayList pubblici; public bool isReversing = false; public int keyframe = 5; private int frameCounter = 0; private int reverseCounter = 0; privato Vector3 currentPosition; private Vector3 previousPosition; private Vector3 currentRotation; private Vector3 previousRotation; macchina fotografica privata; private bool firstRun = true; void Start () keyframes = new ArrayList (); camera = Camera.main;  void Update () if (Input.GetKey (KeyCode.Space)) isReversing = true; camera.GetComponent() .enabled = true; camera.GetComponent() .enabled = true;  else isReversing = false; firstRun = true; camera.GetComponent() .enabled = false; camera.GetComponent() .enabled = false;  void FixedUpdate () if (! isReversing) if (frameCounter < keyframe)  frameCounter += 1;  else  frameCounter = 0; keyframes.Add(new Keyframe(player.transform.position, player.transform.localEulerAngles));   else  if(reverseCounter > 0) reverseCounter - = 1;  else reverseCounter = keyframe; RestorePositions ();  if (firstRun) firstRun = false; RestorePositions ();  float interpolation = (float) reverseCounter / (float) keyframe; player.transform.position = Vector3.Lerp (previousPosition, currentPosition, interpolation); player.transform.localEulerAngles = Vector3.Lerp (previousRotation, currentRotation, interpolation);  if (keyframes.Count> 128) keyframes.RemoveAt (0);  void RestorePositions () int lastIndex = keyframes.Count - 1; int secondToLastIndex = keyframes.Count - 2; if (secondToLastIndex> = 0) currentPosition = (keyframes [lastIndex] come Keyframe) .position; previousPosition = (keyframes [secondToLastIndex] come Keyframe) .position; currentRotation = (keyframes [lastIndex] come Keyframe) .rotation; previousRotation = (keyframes [secondToLastIndex] come Keyframe) .rotation; keyframes.RemoveAt (lastIndex); 

Scarica il pacchetto di build allegato e provalo!

Conclusione

Il nostro gioco time-rewind ora è molto meglio di prima. L'algoritmo è notevolmente migliorato e utilizza il 90% in meno di potenza di elaborazione, è molto più stabile e abbiamo un bel significante che ci dice che stiamo riavvolgendo il tempo.

Ora vai a fare un gioco usando questo!