Konstante Ausführungsgeschwindigkeit von Spielen



  • Hi,

    wie erreicht man eine konstante Ausführungsgeschwindigkeit in einem Spiel? Im Moment versuche ich das über die Framerate, in dem ich sage, dass maximal 50 Frames/Sek. gezeichnet werden sollen, also meine Funktion zum zeichnen des Bildschirminhaltes nur 50 mal aufgerufen wird. In der Zwischenzeit mache ich ein Sleep.

    Das funktioniert bei mir zu Hause soweit ganz, dort komme ich auf 500+ Frames (in der Auflösung die ich verwende), wenn ich jegliche Verzögerung abschalte (dann flitzen aber auch alle Objekt nur so über den Bildschirm). Probiere ich das auf einem älteren Rechner der nur 30 Frames schafft, kriecht natürlich alles (meine gesetzte Marke von 50 Frames schafft er ja auch nicht mal annähernd).

    Wie schafft man das nun, dass sowohl bei 500 als auch bei 30 Frames alles in einer relativ einheitlichen Geschwindigkeit abläuft?

    Danke



  • Ein einfacher Ansatz ist, in jedem Durchlauf die Framedauer zu messen und alle Bewegungen in Abhängigkeit von diesem Wert auszuführen.

    // alle Werte in Millisekunden
    int aktueller_frame, letzter_frame, framedauer;
    aktueller_frame = GET_ZEIT();
    Hauptschleife {
       letzter_frame = aktueller_frame;
       aktueller_frame = GET_ZEIT();
       framedauer = aktueller_frame - letzter_frame;
       // ...
    }
    

    Bewegt sich dann z.B. ein Objekt im Normalfall pro Sekunde um den Vektor (1,1,0), so bewegt es sich nun um (1,1,0) * framedauer/1000.



  • Am einfachsten ist es wohl, wenn du die Zeit seit dem letzten Frame in deine Berechnungen einfließen läßt - auf schnellen Systemen vergeht zwischen den Aufrufen nur wenig Zeit und die Objekte im Spiel müssen nur ein paar Pixel verschoben werden, auf langsamen Systemen "springen" die Objekte von Frame zu Frame über längere Distanzen (das könnte bei sehr langsamen PCs zwar etwas ruckeln, aber die Bewegungsgeschwindigkeit bleibt konstant).



  • Das kann aber schlimme Clippingfehler geben.

    Anderer Ansatz: Man trennt Bewegung von Anzeige mit mehreren Threads und führt die Bewegung anhand der Systemuhr aus.


  • Mod

    Am einfachsten ist es die Logik einfach immer gleich oft pro sekunde aufzurufen, hat auch die besten resultate, da man ja nicht abhaengig vom der geschwindigkeit eines PCs oder von dem was man gerade sieht die logik manipulieren moechte (besonders im hinblick auf physik, z.b. in HL2 der rotor der zombis zerhackt, wenn du das framerate abhaengig machst koennte es stellen geben an denen man im rotor stehen kann ohne dass einem was passiert.

    while(true)
    {
        if(CurrentTime-LastTime>LOGICTIME)
        {
            LastTime=CurrentTime;
            Logic();
        }
        else
            Render();
    }
    


  • danke euch, hab hier auch was zu Timebased Movement befunden...

    http://www.delphigl.com/script/do_show.php?name=bombman2&action=2



  • rapso schrieb:

    Am einfachsten ist es die Logik einfach immer gleich oft pro sekunde aufzurufen

    Exakt. f'`8k

    Gruß, TGGC (\-/ has leading)



  • Dann muss man aber noch darauf achten, dass die Logik nicht zu lange braucht, denn sonst gibts eine Endlosschleife ohne dass etwas gezeichnet wird. Also sollte man eine Sperre einbauen, die aktiv wird, sobald die Logik zu oft hintereinander aufgerufen wird, ohne dass einmal gezeichnet wird. Außerdem kann man, falls der Computer so schnell ist, dass er Logik und Zeichnen schneller schafft, als er muss, noch einfach ein bisschen warten, statt unsinnigerweise zwei mal das selbe Bild zu zeichnen.



  • Wer sagt, das Bild wuerde jedes mal gleich aussehen? f'`8k

    Gruß, TGGC (\-/ has leading)



  • Wenn zwischen zwei mal Zeichnen die Logik nicht ausgeführt wird ändert sich höchstens etwas, was nicht in der Logik berechnet wird. Wenn man sowas nicht hat kann mans auch gleich lassen und für andere Programme etwas Rechenzeit übrig lassen.



  • ... ändert sich höchstens etwas, was nicht in der Logik berechnet wird.

    Genau, das wären dann Animationsfortschritt, Bewegungsfortschritt, GUI (falls animiert), Mauszeiger (falls selber gezeichnet). Ich würde nicht sagen, dass sich das Bild dann nicht vom Vorgänger unterscheidet.


  • Mod

    geloescht schrieb:

    Wenn zwischen zwei mal Zeichnen die Logik nicht ausgeführt wird ändert sich höchstens etwas, was nicht in der Logik berechnet wird.

    ja, die graphik, das tolle ist sogar, jede rechenzeit die du dann hast, witmest du der graphik dann 👍

    Wenn man sowas nicht hat kann mans auch gleich lassen und für andere Programme etwas Rechenzeit übrig lassen.

    ja, textadventure machen das ja auch so.

    aber wo ist nun dein problem? 99% der spiele kommen mit diesem system gut klar.



  • rapso schrieb:

    ja, die graphik, das tolle ist sogar, jede rechenzeit die du dann hast, witmest du der graphik dann 👍

    Diesen Satz verstehe ich beim besten Willen nicht.

    rapso schrieb:

    ja, textadventure machen das ja auch so.

    aber wo ist nun dein problem? 99% der spiele kommen mit diesem system gut klar.

    Ich habe gar kein Problem, ich habe das nur angemerkt. Es ist absolut richtig, solange man logik-unabhängige Bewegungen hat kann man ruhig nochmal zeichnen. Falls man keine hat, ist es nutzlos. Mehr nicht. Die Textadventures haben da doch vollkommen Recht.


  • Mod

    geloescht schrieb:

    rapso schrieb:

    ja, die graphik, das tolle ist sogar, jede rechenzeit die du dann hast, witmest du der graphik dann 👍

    Diesen Satz verstehe ich beim besten Willen nicht.

    was verstehst du denn nicht? dass wenn die logik nicht ausgefuehrt wird, dass sie dann z.B. nur graphik aendert?

    [quote="rapso"]
    Es ist absolut richtig, solange man logik-unabhängige Bewegungen hat kann man ruhig nochmal zeichnen. Falls man keine hat, ist es nutzlos. Mehr nicht.

    nur so als tip: man kann logik abhaengige bewegung zwischen zwei zustaenden auch interpolieren.



  • Man kann aber man muss nicht. Wenn man es tut, dann zeichnet man nochmal, da hab ich garnix dagegen. Außerdem kommt es dann noch darauf an, was du in die Logik und was man in den Graphikcode steckt. Nochmal: Ich habe kein Problem. Das war ein einfacher, mit dem Zusatz, den ich in meinem nächsten Post gemacht habe, gültiger Hinweis.


  • Mod

    geloescht schrieb:

    Man kann aber man muss nicht. Wenn man es tut, dann zeichnet man nochmal, da hab ich garnix dagegen. Außerdem kommt es dann noch darauf an, was du in die Logik und was man in den Graphikcode steckt. Nochmal: Ich habe kein Problem. Das war ein einfacher, mit dem Zusatz, den ich in meinem nächsten Post gemacht habe, gültiger Hinweis.

    und mein zusatz war nur, dass selbst mit logik abhaengiger bewegung trotzdem interpolation zwischen zwei logik-states moeglich ist, sodass man selbst dann noch einen nutzen vom rendering hat und nicht wie du meintest, dass es nutzlos ist.



  • rapso schrieb:

    while(true)
    {
        if(CurrentTime-LastTime>LOGICTIME)
        {
            LastTime=CurrentTime;
            Logic();
        }
        else
            Render();
    }
    

    und wie groß wählt man LOGICTIME? im moment arbeitet ich in 2d (hab erst angefangen mit spieleprogrammierung) und winapi.

    1. TimeStampCounter auslesen
    2. alle Objekte an alter Position im Bild löschen
    3. Bewegung und neue Positionen der Objekte berechnen (Bewegungsoffset inkl.)
    4. alle Objekte an neuer Position in Bild zeichnen
    5. TimeStampCounter erneut auslesen und Bewegungsoffset ermitteln
    6. Bild ausgeben

    das geschieht in einer funktion die immer aufgerufen wird, solange die anwendung aktiv (also nicht minimiert) ist.


  • Mod

    Sunday schrieb:

    rapso schrieb:

    while(true)
    {
        if(CurrentTime-LastTime>LOGICTIME)
        {
            LastTime=CurrentTime;
            Logic();
        }
        else
            Render();
    }
    

    und wie groß wählt man LOGICTIME? im moment arbeitet ich in 2d (hab erst angefangen mit spieleprogrammierung) und winapi.

    1. TimeStampCounter auslesen
    2. alle Objekte an alter Position im Bild löschen
    3. Bewegung und neue Positionen der Objekte berechnen (Bewegungsoffset inkl.)
    4. alle Objekte an neuer Position in Bild zeichnen
    5. TimeStampCounter erneut auslesen und Bewegungsoffset ermitteln
    6. Bild ausgeben

    das geschieht in einer funktion die immer aufgerufen wird, solange die anwendung aktiv (also nicht minimiert) ist.

    das haengt vom spiel ab. bei z.b. strategie und wirtschaftsspielen reicht es mit 25hz (also 40ms), dafuer braucht die logik dann auch oft so einiges davon. bei actionlastigen spielen sind es 30hz(32ms),45hz(22ms) oder 60hz(16ms) die man haben moechte (oft wegen der genauigkeit der physik).
    wenn du ein shot'em up machst, versuch es mit 32ms.



  • Zwischen 10 Hz und 125 Hz hab ich schon gesehen. f'`8k

    Gruß, TGGC (\-/ has leading)



  • rapso schrieb:

    while(true)
    {
        if(CurrentTime-LastTime>LOGICTIME)
        {
            LastTime=CurrentTime;
            Logic();
        }
        else
            Render();
    }
    

    Dazu fallen mir zwei Fragen ein:

    1. Macht es überhaupt Sinn zu rendern, bevor Logic() neu aufgerufen wurde? (z.B. bei Laufzeit(Logic()) << LOGICTIME)
    2. Render() wird nie aufgerufen, wenn Laufzeit(Logic()) > LOGICTIME, also sollte Render() im gleichen Zweig gleich hinter Logic() stehen?

Anmelden zum Antworten