Si desidera separare le frequenze di aggiornamento (tick di logica) e disegnare (render tick).
I tuoi aggiornamenti produrranno la posizione di tutti gli oggetti nel mondo da disegnare.
Tratterò due diverse possibilità qui, quella che hai richiesto, l'estrapolazione e anche un altro metodo, l'interpolazione.
1.
L'estrapolazione è il punto in cui calcoleremo la posizione (prevista) dell'oggetto nel fotogramma successivo, quindi interpoleremo tra la posizione corrente degli oggetti e la posizione in cui l'oggetto sarà nel fotogramma successivo.
Per fare ciò, ogni oggetto da disegnare deve avere un velocity
e associato position
. Per trovare la posizione in cui si troverà l'oggetto nel fotogramma successivo, aggiungiamo semplicemente velocity * draw_timestep
alla posizione corrente dell'oggetto, per trovare la posizione prevista del fotogramma successivo. draw_timestep
è la quantità di tempo trascorsa dal segno di spunta del rendering precedente (ovvero la precedente chiamata di disegno).
Se lo lasci a questo, troverai che gli oggetti "tremolano" quando la loro posizione prevista non corrispondeva alla posizione effettiva nel fotogramma successivo. Per eliminare lo sfarfallio, è possibile memorizzare la posizione prevista e scorrere tra la posizione prevista in precedenza e la nuova posizione prevista in ciascuna fase di disegno, utilizzando il tempo trascorso dal precedente aggiornamento come fattore di lerp. Ciò comporterà comunque un comportamento scadente quando gli oggetti in rapido movimento cambiano improvvisamente posizione e potresti voler gestire quel caso speciale. Tutto quanto detto in questo paragrafo sono i motivi per cui non si desidera utilizzare l'estrapolazione.
2.
L'interpolazione è il luogo in cui memorizziamo lo stato degli ultimi due aggiornamenti e li interpoliamo in base al tempo corrente trascorso dall'aggiornamento precedente all'ultimo. In questa configurazione, ogni oggetto deve avere un position
e associato previous_position
. In questo caso, il nostro disegno rappresenterà, nella peggiore delle ipotesi, un segno di spunta di aggiornamento dietro l'attuale gamestate e, nella migliore delle ipotesi, nello stesso stato esatto del segno di spunta di aggiornamento corrente.
Secondo me, probabilmente vorresti l'interpolazione come l'ho descritta, in quanto è la più facile da implementare delle due, e disegnare una piccola frazione di secondo (ad esempio 1/60 di secondo) dietro il tuo attuale stato aggiornato va bene.
Modificare:
Nel caso in cui quanto sopra non sia sufficiente per consentire l'esecuzione di un'implementazione, ecco un esempio di come eseguire il metodo di interpolazione che ho descritto. Non tratterò l'estrapolazione, perché non riesco a pensare a uno scenario del mondo reale in cui dovresti preferirlo.
Quando si crea un oggetto disegnabile, memorizzerà le proprietà necessarie per essere disegnate (ovvero, le informazioni sullo stato necessarie per disegnarlo).
Per questo esempio, memorizzeremo posizione e rotazione. Potresti anche voler memorizzare altre proprietà come il colore o la posizione delle coordinate della trama (cioè se una trama scorre).
Per impedire che i dati vengano modificati mentre il thread di rendering lo sta disegnando (ovvero la posizione di un oggetto viene modificata mentre il thread di rendering viene disegnato, ma tutti gli altri non sono ancora stati aggiornati), è necessario implementare un tipo di doppio buffering.
Un oggetto ne memorizza due copie previous_state
. Li inserirò in un array e li chiamerò come previous_state[0]
e previous_state[1]
. Allo stesso modo ha bisogno di due copie di esso current_state
.
Per tenere traccia di quale copia del doppio buffer viene utilizzata, memorizziamo una variabile state_index
, disponibile sia per l'aggiornamento che per il thread di disegno.
Il thread di aggiornamento calcola innanzitutto tutte le proprietà di un oggetto utilizzando i propri dati (qualsiasi struttura di dati desiderata). Poi, le copie current_state[state_index]
a previous_state[state_index]
, e copia i nuovi dati necessari per la compilazione, position
e rotation
in current_state[state_index]
. Quindi lo fa state_index = 1 - state_index
, per capovolgere la copia attualmente utilizzata del doppio buffer.
Tutto nel paragrafo precedente deve essere fatto con un lucchetto rimosso current_state
. L'aggiornamento e il disegno dei thread eliminano entrambi questo blocco. Il blocco viene rimosso solo per la durata della copia delle informazioni sullo stato, che è veloce.
Nel thread di rendering, fai quindi un'interpolazione lineare su posizione e rotazione in questo modo:
current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)
Dov'è elapsed
il tempo che è trascorso nel thread di rendering, dall'ultimo tick di aggiornamento, ed update_tick_length
è il tempo che impiega la frequenza di aggiornamento fissa per tick (ad es. Con aggiornamenti 20FPS update_tick_length = 0.05
).
Se non sai quale sia la Lerp
funzione sopra, controlla l'articolo di Wikipedia sull'argomento: Interpolazione lineare . Tuttavia, se non sai cos'è il lerping, probabilmente non sei pronto per implementare l'aggiornamento / disegno disaccoppiato con il disegno interpolato.