C++ Tutorial: Timer 1 Timer v1.0 Einleitung Raum und Zeit sind spätestens seit der kopernikanischen Wende wichtige Gegenstände des Denkens geworden. In einem Programm bestimmt die Zeit die Abläufe und schlussendlich das Erscheinungsbild und spielt besonders in 3D Anwendungen eine extrem wichtige Rolle. Wir wollen uns also eine Art Hilfsbibliothek basteln, die uns alle wichtigen Funktionen rund um die Zeit zur Verfügung stellen soll. Plan / Features Als erstes wollen wir uns überlegen, was wir von einem Timer erwarten. Der Plan wird aufgrund des Umfangs dieses Tutorials entsprechend kurz. Aber warum nicht mal nur Stichworte? - Zeit einstellen / abrufen - Systemzeit (also die unter Windows!) holen - einen Zähler starten - pausieren (z.b. im Pausemenü eines Spiels soll die Zeit ja nicht weiterlaufen!) - vergangene Zeit seit dem letzten Frame abfragen (später dann sehr wichtig für die Transformation der Objekte in Abhängigkeit von der Zeit) - Anzahl Bilder pro Sekunde abfragen - skalieren der Zeit mit einem frei wählbaren Faktor (Bullet-time lässt grüssen!) Hmm... klingt im Moment vielleicht nach viel Arbeit, aber in Wirklichkeit gibt es gar nicht so viel zu tun ;-). Vergangene Zeit vs. Framebremse Im Kern eines jeden 3D Programms sitzt die so genannte Hauptschleife, welche x-mal pro Sekunde durchlaufen wird. Das Ziel ist es ja, eine möglichst hohe Framerate (wie oft das Bild in der Sekunde neu aufgebaut wird, also Anzahl frames per second) zu erreichen. Nun stellen wir uns folgendes vor: Wenn wir das Spiel in jedem Frame um eine Einheit fortbewegen, dann wäre die Bewegung abhängig von der Framerate und somit auch von der Hardware. Das Spiel würde also auf verschiedenen Systemen unterschiedlich schnell laufen, was besonders beim Spielen übers Netzwerk unfair ist. Der mit der ältesten Hardware wundert sich dann, weshalb er immer verliert. Tatsächlich haben sich Entwickler der ersten 3D Anwendungen noch keine Gedanken darüber gemacht und ihre Anwendungen sozusagen dem Rechentakt des Prozessors angepasst. Es gibt verschiedene Ansätze, dieses Problem zu lösen. Der erste (allerdings etwas unökonomische) Ansatz wäre der Einbau einer so genannten Framebremse. Am Ende jedes Hauptschleifendurchgangs wird eine weitere Schleife gestartet, die solange durchlaufen wird (resp. das Programm so lange pausieren lässt), bis exakt 40 Millisekunden seit dem Start des letzten Hauptschleifendurchgangs vergangen sind (1s / 0.04s = 25
C++ Tutorial: Timer 2 fps). Ein wesentlich besserer Ansatz ist das Fortbewegen des Spiels in Abhängigkeit der Zeit, d.h. die vergangene Zeit seit dem letzten Frame wird zum Faktor: Geschwindigkeit [m/s] = Fortbewegungseinheit [m] * vergangene_zeit [s] Bei schneller Hardware ist die vergangene Zeit kleiner und somit wird auch die Geschwindigkeit der Spielfigur verringert. Ergebnis: Gleichberechtigung :-). Implementierung Der folgende Source-Code ist, denke ich mal, genügend kommentiert, sodass ich mir weiteres Gelaber sparen kann ;-). kctimer::kctimer() // zurücksetzen ZeroMemory(this, sizeof(kctimer)); // Variablen auf 'sinnvolle' Startwerte setzen m_dwminute = 0; m_dwhour = 0; m_ffactor = 1.0f; m_fcounterstart = 0.0f; m_fcounterend = 0.0f; m_fcounterfactor = 0.001f; m_dwcounterstarttime = timegettime(); m_fclock = 0.0f; m_llcurtime = 0; m_lllasttime = 0; m_felapsedtime = 1.0f; m_flastelapsedtime = 1.0f; m_fstamp = 0.0f; m_ftime = 0.0f; m_dwfpsupdateinterval = 1000; m_bdelay = FALSE; // Wenn QueryPerformanceFrequency fehlschlägt, dann die etwas // 'ungenauere' Funktion timegettime() nehmen LONGLONG m_llperffreq; if (QueryPerformanceFrequency((LARGE_INTEGER *) &m_llperffreq)) QueryPerformanceCounter((LARGE_INTEGER *) &m_lllasttime); m_bperfflag = TRUE; else m_ftimescale = 1.0f / m_llperffreq; m_bperfflag = FALSE; m_lllasttime = timegettime(); m_ftimescale = 0.001f; // Zeit einstellen void kctimer::settime(int ihours, int iminutes, float ffactor) if (ihours > 23) ihours = 0; if (iminutes > 59)
C++ Tutorial: Timer 3 iminutes = 0; m_dwhour = ihours; m_dwminute = iminutes; // Zähler einstellen (imillis ist die gesamte Zählzeit in ms) void kctimer::setcounter(int istartcount, int iendcount, int imillis) // muss float sein, sonst ist m_ffactor ungültig // ist aber besser int zu übergeben - benutzerfreundlich ;-) m_fcounterstart = (float)istartcount; m_fcounterend = (float)iendcount; float fmillis = (float)imillis; m_fcounterfactor = ((m_fcounterend - m_fcounterstart) / fmillis); m_dwcounterstarttime = timegettime(); // Counterstand holen: wenn Counterstand gleich bleibt, ist das Zählen fertig int kctimer::getcounter() // Datenverlust beabsichtigt! Wir wollen nur ganze Zahlen int icount = (int)(m_fcounterstart + ((timegettime() - m_dwcounterstarttime) * m_fcounterfactor)); if (icount == m_fcounterend) // Werte so setzen, dass Formel oben immer m_fendcount gibt m_fcounterstart = m_fcounterend; m_fcounterfactor = 0.0f; return icount; // Daten updaten (vergangene Zeit ermitteln,...) void kctimer::update() if (m_bperfflag) QueryPerformanceCounter((LARGE_INTEGER *) &m_llcurtime); else m_llcurtime = timegettime(); // soll überhaupt geupdatet werden? if (!m_bdelay) // Vergangene Zeit in Millisekunden berechnen m_felapsedtime = (m_llcurtime - m_lllasttime) * m_ftimescale; m_lllasttime = m_llcurtime; // FramesPerSecond Counter (nur updaten wenn eine gewisse // Anzahl Millisekunden vergangen ist) if ((timegettime() - m_dwstarttime) > m_dwfpsupdateinterval) // Elapsed Time holen und Startzeit neu setzen m_flastelapsedtime = m_felapsedtime; // Uhr neu einstellen m_fclock += (m_felapsedtime * m_ffactor); if (m_fclock >= 1.0f)
C++ Tutorial: Timer 4 m_dwsecond++; m_fclock = 0.0f; if (m_dwsecond >= 60) m_dwminute++; if (m_dwminute >= 60) m_dwhour++; m_dwminute = 0; if (m_dwhour >= 24) m_dwhour = 0; else // TimeStamp einstellen m_fstamp = 0.0f; m_fstamp += m_fclock; m_fstamp += m_dwsecond; m_fstamp += m_dwminute * 100; // 3 Dezimalstellen nach links m_fstamp += m_dwhour * 10000; // 4 Dezimalstellen nach links m_lllasttime = m_llcurtime; m_ftime += m_felapsedtime; // Macht eine Pause void kctimer::wait(int imilliseconds) while (timegettime() < (m_dwstarttime + imilliseconds)) // nichts tun :-) // Liefert die aktuelle Systemzeit SYSTEMTIME kctimer::getsystime(char* pcsystime) // die aktuelle Systemzeit abfragen GetLocalTime(&m_SysTime); Natürlich ist dieser Code noch nicht optimiert und sollte noch um einige Funktionen erweitert werden. Doch für den Anfang dürfte es genug Futter sein, der Rest ist Hausaufgabe.
C++ Tutorial: Timer 5 Quellen - Zerbst, Stefan (2002). 3D Spieleprogrammierung mit DirectX in C++ - Band II. Braunschweig: Books on Demand GmbH Copyright 2005 by Reto Da Forno Irrtümer vorbehalten - Fehler bitte melden! 18.10.05 http://www.keepcoding-development.ch.vu