C++ GameLoop Fragen SFML

Rooky420

Cadet 4th Year
Registriert
Nov. 2015
Beiträge
98
Hallo Zusammen,

ich habe mittlerweile auf vielen Seiten etwas über Gameloops gelesen und habe dann auch nach einiger zeit eine halbwegs vernünftige hinbekommen.

Erst mal der Code:
Code:
void GameManager::GameLoop()
{
	_running = true;

	sf::RenderWindow window(sf::VideoMode(800, 600, 32), "Game");

	const long TARGET_FPS = 60;
	const double NANO_SECOND = 1000000000;
	const __int64 NANO_SECOND_FPS = NANO_SECOND / TARGET_FPS;
	float deltaTime = 1.0f / TARGET_FPS;
	__int64 startFPSTime = GetTimeMS();
	int numFrames = 0;
	while (window.isOpen())
	{
		__int64 frameStartTime = GetTimeNS();

		HandleEvents(&window);

		if (!_running)
			break;

		UpdateGame(deltaTime);
		window.clear();
		RenderGame(&window);
		window.display();
		numFrames++;

		if (GetTimeMS() - startFPSTime >= 1000)
		{
			std::cout << "Frames: " << numFrames << std::endl;
			startFPSTime = GetTimeMS();
			numFrames = 0;
		}
		__int64 neededTimeNS = (GetTimeNS() - frameStartTime);
		//test
		__int64 expectedFramerate = (neededTimeNS * TARGET_FPS) / NANO_SECOND;
		deltaTime = 1.f / expectedFramerate;
		//endtest
		__int64 sleepDuration = NANO_SECOND_FPS - neededTimeNS;
		if (sleepDuration > 0)
		{
			std::this_thread::sleep_for(std::chrono::nanoseconds(sleepDuration));
		}

	}
}

Die Methoden GetTimeMS() bzw. GetTimeNS() liefern einen Zeitstempel in milli- bzw. nanosekunden als __int64.

Fragen:

1)
Sollte ich überhaupt so eine Gameloop verwenden (den test block erst mal ausgeschlossen)?
Ich könnte ja auch einfach so begrenzen:
Code:
window.setFramerateLimit(60);
aber dann habe ich wieder das problem mit dem Updaten da ich keine zeit habe die ich der UpdateGame() methode übergeben kann. Damit währe es dann so dass bei schnelleren pcs das Spiel sich schneller aktualisiert oder wartet es einfach bei
window.display() bis es zeit ist das spiel zu aktualisieren?

2)
HandleEvents(&window);
Dieser Aufruf arbeitet alle events von der warteliste ab.
Code:
void GameManager::HandleEvents(sf::RenderWindow * window)
{
	sf::Event event;
	while (window->pollEvent(event))
	{
		auto screen = screens->GetAktScreen();
		if(screen != NULL)
			screen->Event(&event);
		if (event.type == sf::Event::Closed) 
		{
			delete manager;
			window->close();//Call all Destructors before
		}
	}
}
(Das mit screens und so leitet nur den Aufruf weiter damit nur ein bestimmter Menübildschirm oder ein bestimmter Gamebildschirm updatet wird sonst würde es bei großen Projekten immer alles updaten und das will ich nicht(Performance))
Jetzt zur Frage:
Wenn die Eventschleife zu viel Events zu verarbeiten hat wie z.B. wenn ich das Fenster mit der Maus verschiebe habe ich natürlich massive Framerateeinbußen. Sollte ich daher die Eventschleife in einem anderen Thread oder so ausführen? dann bekomme ich aber wieder Probleme mit Zugriffsfehlern und was weiß ich allem. Oder gibt es eine andere Möglichkeit?

3)
es geht um den Testblock (die zwei Zeilen die in den test Kommentaren stehen)
Sollte ich überhaupt "deltaTime" immer aktualisieren?
Meine Idee war dass es bei jeden Frame die Geschwindigkeit des Updaters anpasst und so zeitliche Bewegungen im spiel noch präziser ausgeführt werden.

Danke und Grüße,
Rooky420
 
Spätestens wenn Du verschiedene Threads asynchron nutzen willst brauchst Du immer die Zeiten. Wird dann aber auch aufwändig die zu synchronisieren.
Statt Frameratelimits per Bibliothek solltest Du selbst schauen wie lange das letzte gerenderte Bild her ist und dann evtl. Deine Renderaufrufe überspringen wenn noch nicht genug Zeit vergangen ist, die restlichen Berechnungen/Inputverarbeitungen kannst und solltest Du dann trotzdem durchführen, dann gibt es keinen Inputlag.
Wenn Du immer die Zeit mitschleifst kannst auch bei bewegten Objekte einfach 4 dimensionale Vektoren nutzen, wobei der 4te der Zeitfaktor ist. So kannst Du zum Beispiel Kamerapfade/Rotationen/etc realisieren die immer zeitlich korrekt berechnet werden unabhängig wieviele FPS jetzt tatsächlich dargestellt wurden.
 
meine Gameloop sieht so aus:
Code:
void Game::run(int frameRate) {
    sf::Clock clock;
    sf::Time deltaFrame{sf::Time::Zero};
    sf::Time minimumFrameRate{sf::seconds(1.f / frameRate)};

    while(_window.isOpen()){
        deltaFrame = clock.restart();
        processEvents();
        if(deltaFrame > minimumFrameRate ){
            update(minimumFrameRate);
        }
        update(deltaFrame);
        render();
    }
}

deltaFrame = clock.restart(); speichert die Zeit, die ein Frame benötigt.
die so ermittelte Frame-Zeit kann man auch leicht alternativ ermitteln:
Code:
sf::Time last = clock.getElapsedTime(); 
while(_window.isOpen()){
    sf::Time now = clock.getElapsedTime(); //rufe aktuelle Zeit auf
    sf::Time deltaFrame = now - last; //ermittle Zeit die einzelner Frame benötigt
    last = now; //speichere Zeit für nächste Kalkulation

    processEvents();
    if(deltaFrame > minimumFrameRate ){
        update(minimumFrameRate);
    }
    update(deltaFrame);
    render();
 etc...
}
 
@Grundkurs
Code:
if(deltaFrame > minimumFrameRate ){
        update(minimumFrameRate);
    }
    update(deltaFrame);
Sicher, dass das das tut, was du erwartest, und dass du nicht doch eher sowas wie

Code:
while (deltaFrame > minimumFrameRate) {
  update(minimumFrameRate);
  deltaFrame -= minimumFrameRate;
}
update(deltaFrame);

meinst? Denn mit deinem aktuellen Code hast du nen ziemlichen zeitlichen Sprung in denen update-Aufrufen, wenn die Framerate mal minimal niedriger ist als minimal vorgesehen.

Rooky420 schrieb:
Wenn die Eventschleife zu viel Events zu verarbeiten hat wie z.B. wenn ich das Fenster mit der Maus verschiebe habe ich natürlich massive Framerateeinbußen.
Das Event-Polling an sich sollte von der Rechenzeit her vernachlässigbar sein (zumal - FPS-Einbrüche, wenn man das Fenster verschiebt? Also wenn das nachher das einzige Problem ist, dann Glückwunsch). Selbst wenn man ein Gamepad angeschlossen hat und da munter an den Sticks dreht, beschränkt sich das auf einige hundert, vielleicht tausend Events pro Sekunde. Worauf es ankommt, ist, was dein Programm damit macht, und ja, sowas kann man dann durchaus von anderen Threads erledigen lassen, wenn sich das vom Aufwand her lohnt.

Was Multithreading generell angeht: Nutze Thread Pools. Relativ einfach zu handhaben und wenn man sich schlau anstellt, skaliert das ganze auch sehr gut. Am Ende eines Frames dann mit allen ausstehenden Aufgaben, die unbedingt vor dem Ende des Frames noch fertig werden müssen, synchronisieren.
 
Zuletzt bearbeitet:
VikingGe schrieb:
@Grundkurs
Sicher, dass das das tut, was du erwartest, und dass du nicht doch eher sowas wie [...] meinst

du hast recht, dein Vorschlag macht tatsächlich mehr Sinn. Danke für den Hinweis! Hoffe trotzdem, dass der Post vom Grundsatz her hilfreich war.
 
Zurück
Oben