Choisissez simplement une fréquence d'images et respectez-la. C'est ce que faisaient beaucoup de jeux à l'ancienne - ils fonctionnaient à un taux fixe de 50 ou 60 FPS, généralement synchronisés avec le taux de rafraîchissement de l'écran, et concevaient simplement leur logique de jeu pour faire tout le nécessaire dans cet intervalle de temps fixe. Si, pour une raison quelconque, cela ne se produisait pas, le jeu aurait juste à sauter une image (ou éventuellement planter), ralentissant efficacement le dessin et la physique du jeu à la moitié de la vitesse.
En particulier, les jeux qui utilisaient des fonctionnalités telles que la détection de collision de sprites matériels devaient à peu près fonctionner comme cela, car leur logique de jeu était inextricablement liée au rendu, qui était effectué dans le matériel à un taux fixe.
Utilisez un pas de temps variable pour votre physique de jeu. Fondamentalement, cela signifie réécrire votre boucle de jeu pour ressembler à ceci:
long lastTime = System.currentTimeMillis();
while (isRunning) {
long time = System.currentTimeMillis();
float timestep = 0.001 * (time - lastTime); // in seconds
if (timestep <= 0 || timestep > 1.0) {
timestep = 0.001; // avoid absurd time steps
}
update(timestep);
draw();
// ... sleep until next frame ...
lastTime = time;
}
et, à l'intérieur update()
, ajuster les formules physiques pour tenir compte du pas de temps variable, par exemple comme ceci:
speed += timestep * acceleration;
position += timestep * (speed - 0.5 * timestep * acceleration);
Un problème avec cette méthode est qu'il peut être difficile de garder la physique (principalement) indépendante du pas de temps ; vous ne voulez vraiment pas que la distance que les joueurs peuvent sauter dépend de leur fréquence d'images. La formule que j'ai montrée ci-dessus fonctionne bien pour une accélération constante, par exemple sous gravité (et celle du poste lié fonctionne assez bien même si l'accélération varie dans le temps), mais même avec les formules physiques les plus parfaites possibles, travailler avec des flotteurs est susceptible de produire un peu de "bruit numérique" qui, en particulier, peut rendre impossible des relectures exactes. Si c'est quelque chose que vous pensez que vous pourriez vouloir, vous pouvez préférer les autres méthodes.
Découplez la mise à jour et dessinez les étapes. Ici, l'idée est de mettre à jour l'état de votre jeu en utilisant un pas de temps fixe, mais d'exécuter un nombre variable de mises à jour entre chaque image. Autrement dit, votre boucle de jeu pourrait ressembler à ceci:
long lastTime = System.currentTimeMillis();
while (isRunning) {
long time = System.currentTimeMillis();
if (time - lastTime > 1000) {
lastTime = time; // we're too far behind, catch up
}
int updatesNeeded = (time - lastTime) / updateInterval;
for (int i = 0; i < updatesNeeded; i++) {
update();
lastTime += updateInterval;
}
draw();
// ... sleep until next frame ...
}
Pour rendre le mouvement perçu plus fluide, vous pouvez également souhaiter que votre draw()
méthode interpole des éléments tels que la position des objets en douceur entre les états de jeu précédent et suivant. Cela signifie que vous devez transmettre le décalage d'interpolation correct à la draw()
méthode, par exemple comme ceci:
int remainder = (time - lastTime) % updateInterval;
draw( (float)remainder / updateInterval ); // scale to 0.0 - 1.0
Vous devrez également faire en sorte que votre update()
méthode calcule l'état du jeu une longueur d'avance (ou éventuellement plusieurs, si vous souhaitez effectuer une interpolation de spline d'ordre supérieur), et lui faire enregistrer les positions précédentes des objets avant de les mettre à jour, afin que la draw()
méthode puisse interpoler entre eux. (Il est également possible d'extrapoler simplement les positions prédites en fonction des vitesses et des accélérations des objets, mais cela peut sembler saccadé, surtout si les objets se déplacent de manière compliquée, entraînant souvent l'échec des prédictions.)
L'interpolation a pour avantage que, pour certains types de jeux, elle peut vous permettre de réduire considérablement le taux de mise à jour de la logique de jeu, tout en conservant une illusion de mouvement fluide. Par exemple, vous pourriez être en mesure de mettre à jour l'état de votre jeu uniquement, disons, 5 fois par seconde, tout en dessinant de 30 à 60 images interpolées par seconde. Dans ce cas, vous pouvez également envisager d'entrelacer votre logique de jeu avec le dessin (c.-à-d. Avoir un paramètre pour votre update()
méthode qui lui dit de ne lancer x % d'une mise à jour complète avant de revenir), et / ou exécuter la physique du jeu / la logique et le code de rendu dans des threads séparés (attention aux pépins de synchronisation!).