Autor Beitrag
Adory
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 16

Win XP
Delphi 7
BeitragVerfasst: So 24.07.11 20:41 
Hallo Leute,

bin gerade dabei ein kleines Spielchen zu entwickeln. Für die Bewegungen möchte ich Frame-Based-Movement wählen - kein Time-Based-Movement. Mein Spiel stellt sehr geringe Anforderungen an die CPU, so dass ich selbst auf meinem 300 Euro Netbook eine Framerate von über 600 erreiche :) Im delphigl-forum habe ich folgenden Codeschnipsel gefunden, aus dem ich jedoch nicht so recht schlau werde:

wiki.delphigl.com/in...Frameratenbegrenzung

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
var
  FMaximumFrameRate: double;
  FLastSleep: double = 0;
 
procedure LimitFrameRate(atd: double); //"atd" ist die Zeitdifferenz zwischen zwei Frames - inklusive der "Sleeptime"
var
  sleeptime: Double;
begin
  sleeptime := 1000 / FMaximumFrameRate - (atd - FLastSleep);
  if sleeptime > 0 then
  begin
    Sleep(trunc(sleeptime));
    FLastSleep := sleeptime;
  end else
    FLastSleep := 0;
end;



Mein bisheriger Gameloop sieht etwa so aus:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedure TMainform.loop(Sender: TObject; var Done: Boolean);
begin
  QueryPerformanceCounter(StartCount);
  Render;
  QueryPerformanceCounter(EndCount);

{...}

  Done := false;
end;



Wie müsste ich obige Prozedur in den Loop integrieren, damit ich eine konstante Framerate von bspw. 60 erhalte? Wie verhält sich das ganze bei gleichzeitig eingeschaltetem VSync? Ist das überhaupt der richtige Ansatz für ein Frame-Based-Movement?

Danke im voraus :wink:
Xion
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
Beiträge: 1952
Erhaltene Danke: 128

Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
BeitragVerfasst: So 24.07.11 22:09 
Du musst im Endeffekt nur die Zeit, die du nicht fürs Rendern benötigst, warten.

WorkTime := EndTime-StartTime ist deine benötigte Zeit. Bei 60 FramesProSekunde(FPS) musst du aber 1/60s pro Frame benötigen. Also musst du die Differenz, also SleepTime := 1/60 - WorkTime; warten.

Man beachte dabei die Zeit-Einheiten, die natürlich übereinstimmen müssen (Sekunden oder Milisekunden).

Sleep(SleepTime); übernimmt das warten. Für diese Zeit wird nichts gemacht, das Programm wartet also bei diesem Befehl (blockiert) und danach geht der Programmfluss normal weiter. Natürlich brauchst du nicht warten, wenn SleepTime<0 (keine Ahnung was dann passiert, vermutlich nichts).

_________________
a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)

Für diesen Beitrag haben gedankt: Adory
Adory Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 16

Win XP
Delphi 7
BeitragVerfasst: So 24.07.11 23:28 
Danke schön :) Ich werd's mal so versuchen...
Regan
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 2157
Erhaltene Danke: 72


Java (Eclipse), Python (Sublimetext 3)
BeitragVerfasst: So 24.07.11 23:29 
user profile iconAdory hat folgendes geschrieben Zum zitierten Posting springen:
Wie müsste ich obige Prozedur in den Loop integrieren, damit ich eine konstante Framerate von bspw. 60 erhalte?

Reguliere den Loop einfach mit einem Timer. Das geht besser als das Warten.

Edit: Mist, zu langsam :(

Für diesen Beitrag haben gedankt: Adory
Adory Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 16

Win XP
Delphi 7
BeitragVerfasst: Mo 25.07.11 00:13 
Hm, also das haut irgendwie nicht hin... was mache ich da falsch? Die Framerate springt jetzt wirr zwischen 200 und 1000 hin und her. Ruckeln ohne Ende :/ Da war sogar meine bisherige Lösung mit Sleep(8 ); :roll: irgendwie effektiver ?


ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure TMainform.loop(Sender: TObject; var Done: Boolean);
begin
  QueryPerformanceCounter(StartCount);
  Render;
  QueryPerformanceCounter(EndCount);

  frametime_ms := (((EndCount - StartCount) / Freq ) * 1000);

  LimitFrameRate(round(1/60 - ((EndCount - StartCount)/Freq)));   // <- ist das überhaupt korrekt so? :o

  Done := false;
end;


Ich schätze mal irgendetwas grundsätzliches habe ich falsch verstanden. Vielleicht kann sich das noch mal jemand ansehen?
Eine weitere Frage.. Angenommen ich möchte das Limit auf 120 Frames haben, der Nutzer hat aber VSync in den Einstellungen seiner Grafikkarte aktiviert (= Anwendung wird standardmäßig auf 60 fps gedrosselt). Könnte man dies für die eigene Anwendung verbieten? wglSwapIntervalEXT(0); hat irgendwie nicht funktionert :/


@Regan: Wie meinst du das genau?
FrEaKY
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 235


D7
BeitragVerfasst: Mo 25.07.11 04:45 
user profile iconAdory hat folgendes geschrieben Zum zitierten Posting springen:
Hm, also das haut irgendwie nicht hin... was mache ich da falsch? Die Framerate springt jetzt wirr zwischen 200 und 1000 hin und her. Ruckeln ohne Ende :/ Da war sogar meine bisherige Lösung mit Sleep(8 ); :roll: irgendwie effektiver ?

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
procedure TMainform.loop(Sender: TObject; var Done: Boolean);
begin
  QueryPerformanceCounter(StartCount);
  Render;
  QueryPerformanceCounter(EndCount);

  frametime_ms := (((EndCount - StartCount) / Freq ) * 1000);     //  <- Richtig!

  LimitFrameRate(round(1/60 - ((EndCount - StartCount)/Freq)));   //  <- Falsch!
  // Warum rundest du? Es wird doch Typ Double verlangt.
  // Außerdem brauchst du den Wert in Millisekunden!

  LimitFrameRate(frametime_ms + FLastSleep);  // Probier das mal.

  Done := false;
end;
Regan
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 2157
Erhaltene Danke: 72


Java (Eclipse), Python (Sublimetext 3)
BeitragVerfasst: Mo 25.07.11 10:12 
user profile iconAdory hat folgendes geschrieben Zum zitierten Posting springen:
@Regan: Wie meinst du das genau?

Ich meine den Abschnitt Timer im Quickstart-Tutorial von DelphiGL. Der Timer ruft alle <n> ms den Loop auf. Wenn du also z. B. 30 Fps (völlig ausreichend) erreichen möchtest, dann stellst du es auf 33 ms ein. Das funktioniert alles ohne Done und ohne das OnIdle der Form.
Ich habe das ganze an meinem eigenen Programm selbst ausprobiert. (Insbesondere dieser Post :P ) Auch ich habe das Ruckeln festgestellt und deshalb den Timer eingebaut.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
TMainform.Timer1Timer(Sender: TObject);
begin
  QueryPerformanceCounter(StartCount);
  Render;
  QueryPerformanceCounter(EndCount);
end;
Xion
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
Beiträge: 1952
Erhaltene Danke: 128

Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
BeitragVerfasst: Mo 25.07.11 11:08 
Ich muss zugeben, ich machs auch immer per Timer ;) Oder (in meinem ersten/einzigen C++ Projekt) ganz ohne Limit.

user profile iconAdory hat folgendes geschrieben Zum zitierten Posting springen:
Eine weitere Frage.. Angenommen ich möchte das Limit auf 120 Frames haben, der Nutzer hat aber VSync in den Einstellungen seiner Grafikkarte aktiviert (= Anwendung wird standardmäßig auf 60 fps gedrosselt). Könnte man dies für die eigene Anwendung verbieten?

Aus welchem Grund willst du doppelt so viel zeichnen, wie angezeigt werden kann?

_________________
a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
Adory Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 16

Win XP
Delphi 7
BeitragVerfasst: Mo 25.07.11 11:25 
Vielen Dank für eure Antworten!! Werde es heut abend ausprobieren.

Zu der Sache mit dem VSync: Wenn ich einen Framelimiter auf 60 fps einbaue (z.B. einfach mit Sleep(xyz)) und VSync ausgeschaltet ist läuft das Spiel wunderbar flüssig auf fast konstanten 60 Frames. Sobald ich VSync aktiviere läuft das Spiel zwar noch immer auf fast konstanten 60 aber es ruckelt alles merkwürdig - es sind ja quasi 2 Framelimiter gleichzeitig am laufen!? Wie bekomme ich dieses Problem in den Griff? Ich könnte auch einfach VSync als Framelimiter allein verwenden (funktioniert auch).. nur sobald ein User VSync explizit abschaltet läuft das Spiel wieder mit 5000 FPS, was ich ja nicht möchte :/

Zu dem Ansatz mit dem Timer: Welchen Timer würdest du genau verwenden? Der Delphi-Interne Timer basiert ja anscheinend auf den Windows-Messages (?) die eine sehr niedrige Prioriät haben. Ich kann mir nicht vorstellen, dass ich damit eine konstante Framerate erhalte. Ich würde es wie gesagt gern mit Queryperformance[..] lösen. Wie schon gesagt, es funktioniert ja auch alles bestens - nur das (unter Umständen aktivierte) VSync macht mir momentan noch einen Strich durch die Rechnung :(


(Es muss doch irgendwie eine Möglichkeit geben eine konstante Framerate ohne ruckeln zu erzielen - unabhängig davon ob VSync nun ein oder ausgeschaltet ist :/)
Xion
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
Beiträge: 1952
Erhaltene Danke: 128

Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
BeitragVerfasst: Mo 25.07.11 12:36 
user profile iconAdory hat folgendes geschrieben Zum zitierten Posting springen:
Zu dem Ansatz mit dem Timer: Welchen Timer würdest du genau verwenden? Der Delphi-Interne Timer basiert ja anscheinend auf den Windows-Messages (?) die eine sehr niedrige Prioriät haben. Ich kann mir nicht vorstellen, dass ich damit eine konstante Framerate erhalte.

Ich verwende den normalen Timer, der in der Tat ungenau ist. Zusätzlich arbeite ich mit Timebased-Movement (es ist also im Endeffekt egal, ob ich einen Framelimiter verwende oder nicht). Wenn du natürlich schon ein Programm hast, dass ohne Timebased-Movement arbeitet, dann wäre der Umbau natürlich schwierig.

_________________
a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)
Adory Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 16

Win XP
Delphi 7
BeitragVerfasst: Mo 25.07.11 13:53 
Aber es muss doch irgendwie möglich sein die Framerate per sleep(x) zu begrenzen, ohne dass mir dabei VSync in die Quere kommt oder? Kann doch nicht sein, dass es daran nun scheitert... :°(


Ach so.. noch etwas zu den FPS. Ich würde gern 120 FPS verwenden, auch wenn es optisch nichts ändert, weil ich so die Kollisionen besser timen kann (ich mache ja kleinere Schritte pro Frame) - aber das wird wohl nichts :/ Ich möchte dem User auch nicht beim öffnen des Programms sagen müssen er möge doch bitte VSync ausschalten :)

---Moderiert von user profile iconNarses: Beiträge zusammengefasst---

 LimitFrameRate(frametime_ms + FLastSleep);

... habe ich eben versucht. Nützt irgendwie nichts. Die Framerate bleibt bei 1000 :/

---Moderiert von user profile iconNarses: Beiträge zusammengefasst---

So langsam wird es immer merkwürdiger. Ich habe mal eben per Sleep() die Framerate auf knapp 30 begrenzt, in der Hoffnung, dass VSync an/aus dann keine Rolle mehr spielt...Die Framerate bleibt natürlich nun auch bei eingeschaltetem VSync auf ca. 30 aber das Spiel läuft spürbar langsamer.. wie kann das sein? Dieses VSync bringt mich noch um die Verstand -.-

---Moderiert von user profile iconNarses: Beiträge zusammengefasst---

Edit:

Sorry, mein Fehler... "LimitFrameRate(frametime_ms + FLastSleep);" war natürlich völlig korrekt. Die FPS werden logischerweise aber falsch angezeigt.. ich rufe die LimitFrameRate-Prozedur ja nicht zwischen StartCount und Endcount auf ;)

Funktioniert soweit zwar, ruckelt leider noch immer :/ Vielleicht hat ja noch jemand eine Idee.. :)
asiekinvette
Hält's aus hier
Beiträge: 3



BeitragVerfasst: Di 09.08.11 12:59 
interessantes Thema, ich werde zur Einreichung von Vorschlägen warten, um das Problem zu lösen

_________________
Ich mag :)


Zuletzt bearbeitet von asiekinvette am Mi 10.08.11 14:36, insgesamt 1-mal bearbeitet