Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: The "Bulletproof" Gameloop?  (Read 11263 times)

0 Members and 1 Guest are viewing this topic.

mateandmetal

  • Full Member
  • ***
  • Posts: 171
  • The bird is the word
    • View Profile
    • my blog
The "Bulletproof" Gameloop?
« on: August 27, 2012, 09:56:18 pm »
Hi There!

I´m redesigning my game loop. Yes, I have read these two famous articles:

Fix Your Timestep!
deWiTTERS Game Loop


I wrote a simple main.cpp file to test this methods. This is my deWitters implementation:

    // Timestep (Constant Game Speed independent of Variable FPS)
    sf::Clock miReloj;

    const int TICKS_POR_SEGUNDO = 25;
    const int SALTEO_TICKS = 1000 / TICKS_POR_SEGUNDO;
    const int MAX_SALTEO_FRAMES = 5;

    int loops;
    float interpolacion;

    sf::Int32 proximo_tick = miReloj.getElapsedTime().asMilliseconds();

// This method doesnt look good with VSync On (jerkiness)
window.setVerticalSyncEnabled (false);


     // Start the game loop
     while (window.isOpen()) {

        // Clear screen
        window.clear();


        // Update (the events are handled in the actualizar function)
        loops = 0;

        while (miReloj.getElapsedTime().asMilliseconds() > proximo_tick && loops < MAX_SALTEO_FRAMES) {

            actualizar (window); // It doesnt use delta time here

            proximo_tick += SALTEO_TICKS;
            ++loops;

        }


        interpolacion = static_cast <float> (miReloj.getElapsedTime().asMilliseconds() + SALTEO_TICKS - proximo_tick) / static_cast <float> (SALTEO_TICKS);


        // Draw
        dibujar (window, interpolacion);

        // Update the window
        window.display();
     }


     return EXIT_SUCCESS;
 


This are my update/drawing functions using deWitters method:
void actualizar (sf::RenderWindow &rw) {

    // Process events
    sf::Event event;
    while (rw.pollEvent (event)) {
     ... // imagine this code :)
    } // end while


    // Update game objects
    if (!seMueve) return;

    if ((posRenderCirc.x > 700.0f) || (posRenderCirc.x < 20.0f)) {
        velCirculo = -velCirculo;
    }

    posRenderCirc.x += velCirculo; // Just add velocity, no dt here

}


void dibujar (sf::RenderTarget &rt, const float dt) {

    // Copy original position
    sf::Vector2f posInterpolada (posRenderCirc);

    // If it´s moving, calculate the interpolated position
    if (seMueve) {
        posInterpolada.x += velCirculo * dt; // velCirculo is a float (the object speed)
    }

    circulo.setPosition (posInterpolada); // Draw object at interpolated position
    rt.draw (circulo);
}
 


This works fine (with VSync off). But I want to allow the user to select if he/she wants to activate or not the vertical synchronization. Ok, this is my GafferonGames method implementation:

    // Timestep (Fix Your Timestep!)
    sf::Clock miReloj;

    const float dt = 1.0f / 30.0f;

    float tiempoActual = miReloj.getElapsedTime().asSeconds();
    float acumulador = 0.0f;

// VSync friendly
window.setVerticalSyncEnabled (false);


     // Start the game loop
     while (window.isOpen()) {

        // Clear screen
        window.clear();

        // Update
        float tiempoNuevo = miReloj.getElapsedTime().asSeconds();
        float tiempoFrame = tiempoNuevo - tiempoActual;

        if (tiempoFrame > 0.25f) {
            tiempoFrame = 0.25f; // Avoid "Spiral of death"
        }

        tiempoActual = tiempoNuevo;
        acumulador += tiempoFrame;

        // Run update in dt sized chunks
        while (acumulador >= dt) {

            actualizar (window, dt);    // using delta time

            acumulador -= dt;

        }

        const float interpolacion = acumulador / dt;

        // Draw with interpolation
        dibujar (window, interpolacion);

        // Update the window
        window.display();
     }


     return EXIT_SUCCESS;
 


The GafferonGames method update/drawing functions:
void actualizar (sf::RenderWindow &rw, const float tiempoDelta) {

    // Process events
    sf::Event event;
    while (rw.pollEvent (event)) {
     ... // super sexy code here
    } // end while


    // Update game objects
    if (!seMueve) return;

    if ((posRenderCirc.x > 700.0f) || (posRenderCirc.x < 20.0f)) {
        velCirculo = -velCirculo;
    }

    posRenderCirc.x += velCirculo * tiempoDelta;

}


void dibujar (sf::RenderTarget &rt, const float alphaInterp) {

    sf::Vector2f posInterpolada (posRenderCirc);

    if (seMueve) {
        posInterpolada.x += alphaInterp;
    }

    circulo.setPosition (posInterpolada);
    rt.draw (circulo);

}
 


This is working good, with VSync on or off. My game update function is running at fixed 30 FPS and the drawing is running at 60 (VSync on)/3500 fps.

So... is the GafferonGames method THE way to go?
Is there any better/optimized/sexy way to do this?

What method are you guys using?
- Mate (beverage) addict
- Heavy metal addict _lml
- SFML 2 addict
- My first (and free) game: BichingISH!

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 11034
    • View Profile
    • development blog
    • Email
Re: The "Bulletproof" Gameloop?
« Reply #1 on: August 27, 2012, 11:15:29 pm »
If you're still looking for the best way of doing things in programming, then you still have to learn one simple lesson: There is no best way for everything! ;)

Programming was 'invented' to solve problems and not the other way around, so one should make it's code match the problem and at the end optimize it for its task.

For most of the simple applications/games I've worked on, I either calculated the latest frametime with a sf::Clock and used it as my delta value, or set a fixed value (e.g. 0.5s) in which I counted the drawn frames and then calculated the FPS and 1/FPS gives the delta time.
Both method work and are simple to implement.
An interesting approach was taken by GQEngine where the update loop runs at a diffrent FPS than the draw loop, with the priority for the update loop, thus if the PC is too slow, then you'd still keep the updates but draw less frames. This also worked fine.
So like I said it's just a matter of what you want to do and how fast you want to get there and there's not really one that fits all. ;)

As for the code, imho it's better to clear the window just before the drawing, since logically clearing is also drawing and I like to keep update and drawing seperated, but I guess it's a matter of taste. :)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

thePyro_13

  • Full Member
  • ***
  • Posts: 156
    • View Profile
Re: The "Bulletproof" Gameloop?
« Reply #2 on: August 28, 2012, 06:30:10 am »
It's all about tradeoffs, if the way your doing it works and doesn't have any drawbacks that effect your game, then it should be fine. Don't change it unless it's getting in your way or slowing you down.

The GQE way is pretty common in the wild. Do your game logic at a constant rate, and use the remaining time to draw as many frames as possible(while updating animations and interloping movement).

mateandmetal

  • Full Member
  • ***
  • Posts: 171
  • The bird is the word
    • View Profile
    • my blog
Re: The "Bulletproof" Gameloop?
« Reply #3 on: August 28, 2012, 06:22:10 pm »
As for the code, imho it's better to clear the window just before the drawing, since logically clearing is also drawing and I like to keep update and drawing seperated, but I guess it's a matter of taste. :)

I will fix that, thanks!


Do your game logic at a constant rate, and use the remaining time to draw as many frames as possible(while updating animations and interloping movement).

Will do, thanks!
- Mate (beverage) addict
- Heavy metal addict _lml
- SFML 2 addict
- My first (and free) game: BichingISH!