J'ai posé quelques questions similaires au cours des 8 derniers mois sans joie réelle, donc je vais rendre la question plus générale.
J'ai un jeu Android qui est OpenGL ES 2.0. en son sein, j'ai la boucle de jeu suivante:
Ma boucle fonctionne sur un principe de pas de temps fixe (dt = 1 / ticksPerSecond )
loops=0;
while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){
updateLogic(dt);
nextGameTick+=skipTicks;
timeCorrection += (1000d/ticksPerSecond) % 1;
nextGameTick+=timeCorrection;
timeCorrection %=1;
loops++;
}
render();
Mon intégration fonctionne comme ceci:
sprite.posX+=sprite.xVel*dt;
sprite.posXDrawAt=sprite.posX*width;
Maintenant, tout fonctionne à peu près comme je le voudrais. Je peux spécifier que je voudrais qu'un objet se déplace sur une certaine distance (largeur d'écran par exemple) en 2,5 secondes et il fera exactement cela. De plus, en raison du saut de trame que j'autorise dans ma boucle de jeu, je peux le faire sur à peu près n'importe quel appareil et cela prendra toujours 2,5 secondes.
Problème
Cependant, le problème est que lorsqu'un cadre de rendu saute, le graphique bégaie. C'est extrêmement ennuyeux. Si je supprime la possibilité de sauter des images, tout est fluide comme vous le souhaitez, mais fonctionnera à différentes vitesses sur différents appareils. Ce n'est donc pas une option.
Je ne sais toujours pas pourquoi le cadre saute, mais je voudrais souligner que cela n'a rien à voir avec de mauvaises performances , j'ai repris le code à 1 petit sprite et sans logique (à part la logique requise pour déplacer le sprite) et je reçois toujours des images sautées. Et c'est sur une tablette Google Nexus 10 (et comme mentionné ci-dessus, j'ai besoin de sauter des trames pour garder la vitesse constante sur tous les appareils).
Donc, la seule autre option que j'ai est d'utiliser l'interpolation (ou l'extrapolation), j'ai lu tous les articles, mais aucun ne m'a vraiment aidé à comprendre comment cela fonctionne et toutes mes implémentations tentées ont échoué.
En utilisant une méthode, j'ai pu faire bouger les choses en douceur, mais c'était impossible car cela a gâché ma collision. Je peux prévoir le même problème avec n'importe quelle méthode similaire parce que l'interpolation est passée à (et appliquée dans) la méthode de rendu - au moment du rendu. Donc, si Collision corrige la position (le personnage se trouve maintenant juste à côté du mur), le rendu peut modifier sa position et la dessiner dans le mur.
Je suis donc vraiment confus. Les gens ont dit que vous ne devriez jamais modifier la position d'un objet depuis la méthode de rendu, mais tous les exemples en ligne le montrent.
Je demande donc un coup de pouce dans la bonne direction, veuillez ne pas créer de lien vers les articles populaires sur la boucle de jeu (deWitters, Fix your timestep, etc.) car j'ai lu ces plusieurs fois . Je ne demande à personne d'écrire mon code pour moi. Expliquez simplement en termes simples comment l'interpolation fonctionne réellement avec quelques exemples. Je vais ensuite essayer d'intégrer toutes les idées dans mon code et poser des questions plus spécifiques si besoin est. (Je suis sûr que c'est un problème avec lequel beaucoup de gens luttent).
Éditer
Quelques informations supplémentaires - variables utilisées dans la boucle de jeu.
private long nextGameTick = System.currentTimeMillis();
//loop counter
private int loops;
//Amount of frames that we will allow app to skip before logic is affected
private final int maxFrameskip = 5;
//Game updates per second
final int ticksPerSecond = 60;
//Amount of time each update should take
private final int skipTicks = (1000 / ticksPerSecond);
float dt = 1f/ticksPerSecond;
private double timeCorrection;
la source
Réponses:
Il y a deux choses cruciales pour que le mouvement paraisse fluide, la première est évidemment que ce que vous effectuez doit correspondre à l'état attendu au moment où l'image est présentée à l'utilisateur, la seconde est que vous devez présenter les images à l'utilisateur à un intervalle relativement fixe. Présenter une trame à T + 10 ms, puis une autre à T + 30 ms, puis une autre à T + 40 ms, apparaîtra à l'utilisateur comme saccadé, même si ce qui est réellement affiché pour ces temps est correct selon la simulation.
Votre boucle principale semble ne pas avoir de mécanisme de déclenchement pour vous assurer que vous effectuez uniquement le rendu à intervalles réguliers. Donc, parfois, vous pouvez faire 3 mises à jour entre les rendus, parfois vous pouvez en faire 4. Fondamentalement, votre boucle sera rendue aussi souvent que possible, dès que vous aurez simulé suffisamment de temps pour pousser l'état de simulation devant l'heure actuelle, vous aurez puis rendez cet état. Mais toute variabilité du temps de mise à jour ou de rendu et l'intervalle entre les images varient également. Vous avez un pas de temps fixe pour votre simulation, mais un pas de temps variable pour votre rendu.
Ce dont vous avez probablement besoin, c'est d'une attente juste avant votre rendu, ce qui garantit que vous ne commencerez jamais le rendu qu'au début d'un intervalle de rendu. Idéalement, cela devrait être adaptatif: si vous avez mis trop de temps à mettre à jour / rendre et que le début de l'intervalle est déjà passé, vous devez rendre immédiatement, mais aussi augmenter la longueur de l'intervalle, jusqu'à ce que vous puissiez rendre et mettre à jour de manière cohérente et toujours accéder à le rendu suivant avant la fin de l'intervalle. Si vous avez beaucoup de temps à perdre, vous pouvez réduire lentement l'intervalle (c.-à-d. Augmenter la fréquence d'images) pour rendre à nouveau plus rapidement.
Mais, et voici le kicker, si vous ne restituez pas l'image immédiatement après avoir détecté que l'état de simulation a été mis à jour "maintenant", vous introduisez un alias temporel. Le cadre présenté à l'utilisateur est présenté au mauvais moment, et cela se sentira comme un bégaiement.
C'est la raison du «pas de temps partiel» que vous verrez mentionné dans les articles que vous avez lus. C'est là pour une bonne raison, et c'est parce que si vous ne fixez pas votre pas de temps physique à un multiple entier fixe de votre pas de temps de rendu fixe, vous ne pouvez tout simplement pas présenter les images au bon moment. Vous finissez par les présenter trop tôt ou trop tard. La seule façon d'obtenir un taux de rendu fixe et de toujours présenter quelque chose de physiquement correct est d'accepter qu'au moment où l'intervalle de rendu arrive, vous serez très probablement à mi-chemin entre deux de vos pas de temps fixes en physique. Mais cela ne signifie pas que les objets sont modifiés lors du rendu, juste que le rendu doit établir temporairement où se trouvent les objets pour qu'il puisse les rendre quelque part entre où ils étaient avant et où ils se trouvent après la mise à jour. C'est important - ne changez jamais l'état du monde pour le rendu, seules les mises à jour devraient changer l'état du monde.
Donc, pour le mettre dans une boucle de pseudocode, je pense que vous avez besoin de quelque chose de plus comme:
Pour que cela fonctionne, tous les objets mis à jour doivent conserver la connaissance de leur emplacement précédent et de leur emplacement actuel, afin que le rendu puisse utiliser sa connaissance de l'emplacement de l'objet.
Et définissons une chronologie en millisecondes, en disant que le rendu prend 3 ms pour terminer, la mise à jour prend 1 ms, votre pas de temps de mise à jour est fixé à 5 ms et votre pas de temps de rendu commence (et reste) à 16 ms [60 Hz].
Il y a une autre nuance ici à propos de la simulation trop à l'avance, ce qui signifie que les entrées de l'utilisateur peuvent être ignorées même si elles se sont produites avant que le cadre ne soit réellement rendu, mais ne vous inquiétez pas jusqu'à ce que vous soyez sûr que la boucle se simule correctement.
la source
Ce que tout le monde vous a dit est correct. Ne mettez jamais à jour la position de simulation de votre sprite dans votre logique de rendu.
Pensez-y comme ceci, votre sprite a 2 positions; où la simulation indique qu'il est à la dernière mise à jour de la simulation et où le sprite est rendu. Ce sont deux coordonnées complètement différentes.
Le sprite est rendu à sa position extrapolée. La position extrapolée est calculée à chaque image de rendu, utilisée pour rendre l'image-objet, puis jetée. C'est tout ce qu'on peut en dire.
À part cela, vous semblez avoir une bonne compréhension. J'espère que cela t'aides.
la source