Framerate...



  • Hallo zusammen...
    Ich bin dran, ein 2D-Jump'n'Run zu programmieren... Ich hab mir auch schon überlegt, wie ich das mit der Framerate anstellen soll. Dabei bin ich auf mehrere Methoden gestossen...

    1. In einem Tutorial (jnrdev.72dpiarmy.com) ist so gehandhabt worden, dass am Start der Hauptschleife die momentane Zeit über SDL_GetTicks() ermittelt wird und am Ende der Schleife so lange gewartet wird, bis eine Anzahl Millisekunden verstrichen ist:

    bool running = true;
    int framestart, WaitTime;
    while(running)
    {
        framestart = SDL_GetTicks();
    
        // Aktionen der Hauptschleife
    
        while (SDL_GetTicks() - framestart < WaitTime);
    }
    

    Eigentlich find ich diese Methode nicht so schlecht, ich hab mir jedoch überlegt, dass sie zu Problemen führen kann, wenn für ein Frame mehr Zeit gebraucht wird als WaitTime...

    2. Eine andere Möglichkeit wäre, die Geschwindigkeit der Figuren einfach der Framerate anzupassen...

    int curframe = SDL_GetTicks(), lastframe, frametime;
    while (running)
    {
        lastframe = curframe;
        curframe = SDL_GetTicks();
        frametime = curframe - lastframe;
    
        // Aktionen der Hauptschleife 
        v *= frametime;    // vereinfacht: eine Geschwindigkeit wird mit der Zeit für ein Frame multipliziert
    }
    

    3. Ein Zähler, der direkt misst, wie viele FPS vorkommen (ähnelt Methode 1):

    int FPS = 300;               // einfach ein bestimmter Anfangswert, der etwa dem Durchschnitt 
                                 // aller Frameraten in mehreren Sekunden entspricht
    int framesCount = 0;
    int starttime = SDL_GetTicks();
    while (running)
    {
    
        if (SDL_GetTicks() - starttime < 1000)
            framesCount++;
        else
        {
            FPS = framesCount;
            framesCount = 0;
            starttime = SDL_GetTicks()
        }
    
        // Aktionen der Hauptschleife
        v /= FPS;               // wieder vereinfacht: Geschwindigkeit wird durch Framerate dividiert
    

    Ein grosses Problem bei dieser Methode entsteht dadurch, dass nur jede Sekunde gemessen wird und deshalb nicht auf jedes Frame direkt reagiert werden kann...

    Es gibt auch noch viele andere Methoden, einige hab ich auch schon gesehen, ich hab hier einfach mal drei davon aufgeschrieben (ich bin mir bewusst, dass sie nicht optimal sind und im Übrigen auch Fehler enthalten können, denn ich hab diese jetzt aus dem Kopf aufgeschrieben, ohne sie zu testen... ;))

    Jetzt frage ich euch, wie ihr es mit der Framerate handhabt... Ich denke, z.B. meine 2. Möglichkeit ist zu gebrauchen, die erste vielleicht auch...
    Ich bin eher Anfänger, was Frameraten etc. betrifft, deshalb bitte ich um euren Rat und um Vorschläge, wie solche Methoden noch optimiert werden können.
    Vielen Dank schon im Voraus...

    P.S. An alle, die denken, ich sei einer, der einfach mal drauflos schreibt ohne zu suchen, ob schon andere ähnliche Beiträge existieren: Ich hab im Forum herumgeschaut und auch Zeugs gefunden (z.B. Methode 2 hab ich von hier). Ich wollte einfach 3 unterschiedliche Möglichkeiten vergleichen und euch fragen, was ihr dazu meint 🙂



  • also ich würde frames und gameticks unterscheiden

    in frames machst du nicht-game-logik sachen wie animation
    (const float FPS = 20;
    DWORD frameNum = (DWORD)(gTimer->getTime() * FPS) % mFrames.size();)

    in gameticks machst du spielmechanik

    und anstelle zeit mit einer busyloop zu verbraten, teste in jedem frame ob ein neuer gametick gemacht werden muss oder nicht

    falls die frames mehr zeit brauchen als der abstand zwischen 2 gameticks zuckelt es natürlich, aber das kann man nicht verhindern



  • //framerate Variblen (einmal bestimmen, zB am anfang des renderer Threads)
        double fps=0;
        double lasttick=GetTickCount();
        double currenttick=0;
        int fsptoshow;
    //framerate Messen
        nextfps:
        fps++;
        currenttick=GetTickCount();
        if((currenttick-lasttick)>1000)
        {
            fpstoshow=fps;
            lasttick=currenttick;
            fps=0;
        }
    //////////////////////
    //szene rendern usw //
    //////////////////////
        if(running==true)
        {
            goto nextfps;
        }
    //Program/renderer shutdown
    


  • Warum benutzt du goto, wenn eine einfache while Schleife auch geht?
    Warum fragst du einen boolschen Wert auf true ab?

    if (running == true) ...;
    

    Das ist doch redundant.

    Ich würde es ja ungefähr so aufbauen:

    while (running)
    {
        fps++; 
        currenttick=GetTickCount(); 
        if((currenttick-lasttick)>1000) 
        { 
            fpstoshow=fps; 
            lasttick=currenttick; 
            fps=0; 
        } 
        ////////////////////// 
        //szene rendern usw // 
        ////////////////////// 
    }
    

    Gruß
    Don06



  • Don06 schrieb:

    Warum benutzt du goto, wenn eine einfache while Schleife auch geht?
    Warum fragst du einen boolschen Wert auf true ab?
    ....
    Gruß
    Don06

    Angewohnheit xD



  • @ Don06:
    Ich finde, deine Methode sieht meiner 3. Möglichkeit recht ähnlich... Du benötigst doch fpstoshow für deine Spielgeschwindigkeit, oder irre ich mich?
    Und wie ist das mit GetTickCount()? Gibt es den gleichen Rückgabewert wie SDL_GetTicks()?
    Und dafür muss man doch time.h inkludieren, oder?



  • Es ist besser Grafik-Frames von GameTicks zu trennen, wie prim0 schon sagte.
    Hier mal ein Beispiel (Pseudo-Code):

    int lastTime = GetTickCount();
    // Game-Loop
    while (true)
    {
        int currentTime = GetTickCount();
        if ( (currentTime - lastTime) > 40 ) // alle 40ms: Game-Logik
        {
             lastTime = currentTime;
             // Hier: Spiel-Logik wie Bewegungen, Kollisionen, usw.
             // Dieser Block hat eine konstante "Framerate" von 25 FPS
             // Spielbewegungen sind daher immer gleichmäßig außer die
             // Framerate sinkt unter 25 FPS.
        }
        // FPS berechnen,
        // Szene rendern,
        // ...
    }
    

    Es ist wichtig Grafik und Logik zu trennen. Bewegungen sollten unabhängig von der Leistung der Grafikkarte durchgeführt werden.
    Ausserdem ist es unnötig 100-150 mal in der Sekunde zu überprüfen, ob der Spieler gerade Esc gedrückt hat, da niemand eine Taste sooft in der Sekunde drücken kann. Genauso mit Kollisionen: Wieso sollte man 100-150 mal in der Sekunde überprüfen, ob zwei Gegenstände kollisiert sind?
    Die unnötigen Überprüfungen würde auch noch Performance kosten.

    Achja: Benutze lieber deine SDL_GetTicks()-Funktion. GetTickCount ist glaube ich OS-abhängig.

    Gruß
    Don06



  • Ok, danke für die Hilfe 😉



  • Sorry, aber einige Dinge versteh ich nicht ganz:
    Die Szene zu rendern braucht ja ziemlich viel Rechenzeit, wieso also ausserhalb des 40-ms-Abfrageblocks? Und das Aktualisieren und Neu Zeichnen geschieht sowieso nur auf Ereignisse der Spiellogik... Ich hab es mal so implementiert (bestimmt falsch xD), dass beide im Innenblock sind, es hat jedoch visuell nichts geändert, als ich die Renderingsfunktion in die Hauptschleife verschoben habe. Dazu kommt, dass bei mir ein leichtes Flackern etwa jede halbe Sekunde auftritt (auch mit Double Buffering).

    Ausserdem finde ich 40 ms viel zu lang, ich habs mit 5 gemacht, weil sonst das Game extrem stockt... zwar hat 5ms keine grosse Wirkung mehr, aber ja...

    Hier mal der Code:

    SDL_Event event;    
    bool running = true;
    int LastTime = SDL_GetTicks();    
    while (running)
    {
        int CurrentTime = SDL_GetTicks();
        if (CurrentTime - LastTime > 5)
        {
            LastTime = CurrentTime;
            SDL_PollEvent(&event);
            if (event.type == SDL_QUIT || key[SDLK_ESCAPE])
                running = false;
            player->Think(); // Funktion, die alle Eingaben, Bewegung und Kollisionsabfragen übernimmt
            player->Display(background); // Funktion, die die Figur neu zeichnet (momentan noch nur die Figur)
        }
        // ***
    }
    

    Bei den drei Sternchen hast du geschrieben, da käme FPS berechnen, Szene rendern etc. Wieso ist denn die FPS-Rate noch zu berechnen?



  • Es flackert, weil die Szene ausserhalb des Blocks berechnet werden sollte.

    Das, was du gebaut hast ist eine Frame-Bremse. Sie reduziert auf guten Rechnern die Frame-Rate -> Grafik ist schlechter als möglich. Ausserdem bietet das auch für schlechtere Rechner keinen Vorteil -> ruckelige Grafik.
    Die Grafik-Frames konstant zu machen, ist ein Parade-Beispiel für "An-der-falschen-Stelle-Optimieren". (Ausnahme bildet hier natürlich VSync, oder änliches)

    Und zu der Frame-Rate: Es gibt zwei unterschidliche Dinge: Grafik-Frames und Logik-Ticks. Während die Logik-Ticks in einem gewissen Grade konstant sind (40ms -> 25 Ticks pro Sekunde), sind es die Grafik-Frames keinesfalls. Bei aufwendigen Szenen, viel KI oder Ragdoll-Effekten sinkt die Frame-Rate, bei weniger steigt sie. Das kennt man ja aus fast jedem 3D-Spiel (Bei zig Gegner und viel Spezial-Effekten ruckelts halt mal).

    Ich hoffe das hilft dir ein bisschen.

    Gruß
    Don06



  • ich sag mal, auch für die darstellung von grafischen spielen lässt sich das model-view-controller konzept durchaus gut verwenden.

    der view ist hier die grafikengine, die sich um das berechnen der darzustellenden entitäten kümmert. das model ist der zustand der aktuellen szene.
    der view kann unabhängig vom model agieren, da es sich nur der daten aus dem model bedient.
    wichtigstes element ist der controller. dieser hat wissen über die nötigen ticks und "füttert" das model mit den nötigen daten. soll also beispielsweise eine animation dargestellt werden (die spielfigur springt), dann weiss der controller, dass diese animation z.b. 20 ticks dauern soll.

    ist es nun möglich, diese 20 animationsstufen innerhalb von 20 ticks zu berechnen, ist alles in butter (sozusagen). der controller kann die animationsschritte durchaus auch schneller berechnen (alle 20) und aktualisiert das model dann halt entsprechend den ticks. so ist die gesamte animation möglicherweise schon nach 3 ticks berechnet und der controller ist wieder frei, sich um weitere aufgaben zu kümmern. denn das model um die bereits fertig berechneten zustände zu aktualisieren, sollte sogut wie keine rechnenzeit beanspruchen.



  • Aber was ist nun an meinem Programm konkret falsch? Liegt der einzige Fehler darin, dass das Rendern ausserhalb des 40-ms-Blockes sein sollte (ich hab es auch probiert, es flackerte weiterhin)?
    Und wenn bei mir die Spiellogik nur alle 40 ms berechnet wird, ist das Game zu langsam, bzw. es gibt zu harte Übergänge (die Figur verschiebt sich um 5 Pixel auf einmal etc.) Und damit wir uns nicht missverstehen: Die Spiellogik (Model) umfasst schon alles zum Berechnen (also Ändern der Koordinaten, Eingaben, Kollisionsabfragen) und mit dem View (Grafischen) wird nur das Blitten und Updaten des Screens bezeichnet?

    Sorry wenn ich mich wie ein Noob anstelle, doch ich kann es mir zum Teil noch nicht richtig vorstellen...



  • Das Problem besteht weiterhin, könntet ihr mir konkrete Korrekturvorschläge machen? Was muss den genau geändert werden? Ausserdem stockt das Spiel zum Teil enorm, also sieht man z.B. die Figur, wie sie stehen bleibt, und plötzlich ist sie um 20 Pixel auf einmal verschoben...

    Hier ist der Code (ich hab die Display-Funktion aus dem 40-ms-Block herausgenommen), die oben erwähnten Probleme treten immer noch auf; bitte konkrete Verbesserungsvorschläge! Danke im Voraus!

    SDL_Event event;    
    bool running = true;
    int LastTime = SDL_GetTicks();    
    while (running)
    {
        int CurrentTime = SDL_GetTicks();
        if (CurrentTime - LastTime > 5)
        {
            LastTime = CurrentTime;
            SDL_PollEvent(&event);
            if (event.type == SDL_QUIT || key[SDLK_ESCAPE])
                running = false;
            player->Think(); // Funktion, die alle Eingaben, Bewegung und Kollisionsabfragen übernimmt
            }
        player->Display(background); // Funktion, die die Figur neu zeichnet (momentan noch nur die Figur)
    
    }
    

Anmelden zum Antworten