Vous souhaitez séparer les taux de mise à jour (tick logique) et de dessin (tick de rendu).
Vos mises à jour produiront la position de tous les objets dans le monde à dessiner.
Je couvrirai ici deux possibilités différentes, celle que vous avez demandée, l'extrapolation, et aussi une autre méthode, l'interpolation.
1.
L'extrapolation est l'endroit où nous calculerons la position (prédite) de l'objet à l'image suivante, puis interpolerons entre la position actuelle des objets et la position que l'objet sera à l'image suivante.
Pour ce faire, chaque objet à dessiner doit avoir un velocity
et associé position
. Pour trouver la position que l'objet sera à l'image suivante, nous ajoutons simplement velocity * draw_timestep
à la position actuelle de l'objet, pour trouver la position prédite de l'image suivante. draw_timestep
est le temps qui s'est écoulé depuis le tick de rendu précédent (alias l'appel de dessin précédent).
Si vous vous en tenez à cela, vous constaterez que les objets "scintillent" lorsque leur position prédite ne correspond pas à la position réelle à l'image suivante. Pour supprimer le scintillement, vous pouvez stocker la position prédite et lerp entre la position prédite précédemment et la nouvelle position prédite à chaque étape de dessin, en utilisant le temps écoulé depuis la dernière mise à jour cocher comme facteur lerp. Cela se traduira toujours par un mauvais comportement lorsque des objets en mouvement rapide changent soudainement d'emplacement, et vous voudrez peut-être gérer ce cas spécial. Tout ce qui est dit dans ce paragraphe est la raison pour laquelle vous ne voulez pas utiliser d'extrapolation.
2.
L'interpolation est l'endroit où nous stockons l'état des deux dernières mises à jour et nous les interpolons en fonction du temps actuel écoulé depuis la dernière mise à jour. Dans cette configuration, chaque objet doit avoir un position
et associé previous_position
. Dans ce cas, notre dessin représentera au pire un tick de mise à jour derrière le gamestate actuel, et au mieux, exactement au même état que le tick de mise à jour actuel.
À mon avis, vous voulez probablement une interpolation telle que je l'ai décrite, car c'est la plus facile des deux à implémenter, et dessiner une infime fraction de seconde (par exemple 1/60 seconde) derrière votre état actuel mis à jour est très bien.
Modifier:
Dans le cas où ce qui précède ne suffit pas pour vous permettre d'effectuer une implémentation, voici un exemple de la façon de faire la méthode d'interpolation que j'ai décrite. Je ne couvrirai pas l'extrapolation, car je ne pense à aucun scénario du monde réel dans lequel vous devriez le préférer.
Lorsque vous créez un objet dessinable, il stockera les propriétés nécessaires à dessiner (c'est-à-dire les informations d' état nécessaires pour le dessiner).
Pour cet exemple, nous allons stocker la position et la rotation. Vous pouvez également vouloir stocker d'autres propriétés comme la position des coordonnées de couleur ou de texture (c'est-à-dire si une texture défile).
Pour éviter que les données ne soient modifiées pendant que le thread de rendu les dessine (c'est-à-dire que l'emplacement d'un objet est modifié pendant que le thread de rendu dessine, mais tous les autres n'ont pas encore été mis à jour), nous devons implémenter un certain type de double tampon.
Un objet en stocke deux copies previous_state
. Je vais les mettre dans un tableau et les désigner comme previous_state[0]
et previous_state[1]
. De même, il en a besoin de deux copies current_state
.
Pour garder une trace de la copie du double tampon utilisée, nous stockons une variable state_index
, qui est disponible à la fois pour le fil de mise à jour et de dessin.
Le thread de mise à jour calcule d'abord toutes les propriétés d'un objet en utilisant ses propres données (toutes les structures de données que vous souhaitez). Ensuite, il copie current_state[state_index]
à previous_state[state_index]
, et copie les nouvelles données pertinentes pour le dessin, position
et rotation
en current_state[state_index]
. Ensuite, il state_index = 1 - state_index
retourne la copie actuellement utilisée du double tampon.
Tout dans le paragraphe ci-dessus doit être fait avec un verrou retiré current_state
. Les fils de mise à jour et de dessin retirent tous les deux ce verrou. Le verrou n'est retiré que pendant la durée de la copie des informations d'état, ce qui est rapide.
Dans le fil de rendu, vous effectuez ensuite une interpolation linéaire sur la position et la rotation comme suit:
current_position = Lerp(previous_state[state_index].position, current_state[state_index].position, elapsed/update_tick_length)
Où elapsed
est le temps qui s'est écoulé dans le fil de rendu, depuis la dernière mise à jour, et update_tick_length
le temps que met votre taux de mise à jour fixe par tick (par exemple, lors des mises à jour 20FPS, update_tick_length = 0.05
).
Si vous ne savez pas quelle est la Lerp
fonction ci-dessus, consultez l'article de wikipedia sur le sujet: Interpolation linéaire . Cependant, si vous ne savez pas ce qu'est le lerping, vous n'êtes probablement pas prêt à implémenter la mise à jour / dessin découplé avec le dessin interpolé.