• Keine Ergebnisse gefunden

Kontakt und Updates

1.4 Wir bauen uns eine eigene Engine!

1.4.5 Vorgabefunktionen, Klassen und Makros

1.4.5.6 Die Nachrichtenschleife

Die Nachrichtenschleife, die wir bereits früher in diesem Kapitel besprochen haben, existiert in ähnlicher Form auch in der TriBase-Engine. Mit der Funktion tbDoMessageLoop wird sie aus-geführt. Diese Funktion arbeitet mit Funktionszeigern. Man kann ihr praktisch als Parameter zwei selbst definierte Funktionen übergeben, die dann von ihr aufgerufen werden (zum Bewe-gen und zum Rendern).

Tabelle 1.8 Die Parameter der Funktion tbDoMessageLoop

Parameter Beschreibung

tbResult (* pMoveProc)(float) Ein Zeiger auf die benutzerdefinierte Move-Funktion, die einen float-Parameter erwartet (die vergangene Zeit seit dem letzten Bild, angegeben in Sekunden). Diese Funktion wird einmal pro Bild aufgerufen. NULL bedeutet, dass es keine Move-Funktion gibt.

tbResult (* pRenderProc)(float) Zeiger auf die Render-Funktion mit einem float-Parameter (ver-gangene Sekunden), die einmal pro Bild aufgerufen wird. NULL: keine Render-Funktion.

Die Funktion wird verlassen, wenn das Fenster der TriBase-Engine geschlossen wurde oder wenn eine der beiden angegebenen Funktionen den Rückgabewert TB_STOP liefert. Das folgen-de Listing folgen-demonstriert folgen-den Umgang mit folgen-der neuen Funktion.

tbResult Move(float fTime) {

// Das ist die Move-Funktion. fTime beinhaltet die vergangenen Sekunden seit dem // letzten Bild. Um so viele Einheiten müsste das Spiel nun fortbewegt werden.

return TB_OK;

}

tbResult Render(float fTime) {

// Das ist die Render-Funktion. Hier würde normalerweise die Spielsituation gezeichnet.

return TB_OK;

}

// Nachrichtenschleife einleiten und die beiden Funktionen angeben tbDoMessageLoop(Move, Render);

Listing 1.15 Anwendung der Funktion tbDoMessageLoop

1.4.5.7 Hilfsmakros

Sicherheitsmakros

Wenn man versucht, eine Methode (zum Beispiel Release) einer nicht existenten COM-Schnittstelle aufzurufen, dann endet das meistens in einem Programmabsturz.

Damit man nicht immer prüfen muss, ob eine Schnittstelle oder allgemein ein Zeiger gleich

NULL ist, legen wir uns für die Engine einige Makros an: eines für das „sichere“ Löschen eines mit dem new-Operator angelegten Speicherbereichs, ein Makro zum sicheren Löschen eines ganzen Arrays (ebenfalls mit new angelegt) und eines für das sichere Freigeben einer COM-Schnittstelle (Release). Auch Speicherbereiche, die mit der Funktion tbMemAlloc oder

tbMemReAlloc reserviert wurden, können mit Hilfe eines weiteren Makros sicher gelöscht wer-den. Dabei wird geprüft, ob es sich um einen Nullzeiger handelt. Falls ja, geschieht nichts.

Anderenfalls wird das Objekt gelöscht und der Zeiger auf NULL gesetzt. So wird verhindert, dass dasselbe Objekt „mehrfach gelöscht“ werden kann.

#define TB_SAFE_DELETE(x) {if((x)) {delete (x); (x) = NULL;}}

#define TB_SAFE_DELETE_ARRAY(x) {if((x)) {delete[] (x); (x) = NULL;}}

#define TB_SAFE_RELEASE(x) {if((x)) {(x)->Release(); (x) = NULL;}}

#define TB_SAFE_MEMFREE(x) {if((x)) {tbMemFree((x)); (x) = NULL;}}

Listing 1.16 Vier Makros für mehr „Sicherheit“

Das Löschen eines Nullzeigers mit delete oder delete[] ist zwar eigentlich harmlos, aber um einheitlich zu bleiben, gibt es diese Makros trotzdem.

Minimum und Maximum

Neben diesen Makros gibt es noch ein paar weitere: TB_MIN und TB_MAX (Minimum und Maxi-mum zweier Werte von beliebigem Typ berechnen) sowie drei Makros zum Umgang mit Winkeln (Umrechnung aus dem Bogenmaß (Rad) ins Gradmaß und umgekehrt sowie die Kreiszahl π (Pi)).

#define TB_MIN(a, b) ((a) < (b) ? (a) : (b))

#define TB_MAX(a, b) ((a) > (b) ? (a) : (b))

#define TB_PI (3.1415926535897932384626433832795f)

#define TB_DEG_TO_RAD(x) ((x) * 0.0174532925199432957692369076848f)

#define TB_RAD_TO_DEG(x) ((x) * 57.295779513082320876798154814105f)

Listing 1.17 Die Makros für das Minimum, das Maximum, Pi (π) und Winkelumrechnungen – die vielen Nachkommastellen sind natürlich nicht ganz ernst gemeint.

Fehlermakros

TriBase-Funktionen prüfen bei so gut wie jedem Aufruf einer anderen Funktion, ob dieser auch erfolgreich war. Wenn das nicht der Fall war, dann wird das in der Logbuchdatei ver-merkt, und die Funktion schlägt üblicherweise fehl. Gleiches gilt auch bei falsch angegebenen Parametern (zum Beispiel Nullzeiger oder ungültige Werte wie eine negative Speichergröße).

Durch all die dadurch erforderlichen Vergleiche und Prüfungen wird der Code nicht gerade einfacher zu lesen. Darum gibt es einige vordefinierte Fehlermakros, die den Logbuchvermerk vornehmen und die Funktion durch Rückgabe von TB_ERROR oder eines anderen Wertes schei-tern lassen. In der Logbuchdatei wird dann automatisch der Ort des Fehlers vermerkt (Datei-name, Zeile und Funktionsname). Manche Makros erwarten noch einen zusätzlichen Parame-ter – zum Beispiel einen Dateinamen bei dem Fehler „Datei FRITTENBUDE.DAT konnte nicht geöffnet werden!“. Die folgende Auflistung zeigt jedes Makro mit dem generierten Log-buchdateieintrag.

ƒ TB_ERROR_NULL_POINTER(x, r): „FEHLER: (x) ist NULL!“ return r;

ƒ TB_ERROR_INVALID_VALUE(x, r): „FEHLER: (x) hat einen ungültigen Wert!“ … return r;

ƒ TB_ERROR_OUT_OF_MEMORY(x, r): „FEHLER: Nicht genug Speicher!“ … return r;

ƒ TB_ERROR_FILE_FILE(f, r):

„FEHLER: Die Datei (f) konnte nicht geöffnet, gelesen, erstellt oder beschrieben werden!“

return r;

ƒ TB_ERROR_RESOURCE(n, t, r):

„FEHLER: Fehler beim Zugreifen auf die Ressource (n) vom Typ (t)!“ … return r;

ƒ TB_ERROR(x, r): „FEHLER: (x)“ … return r;

ƒ TB_ERROR_MESSAGE(x): „FEHLER: (x)“ (ohne return)

ƒ TB_INFO(x): „INFO: (x)“

ƒ TB_WARNING(x): „WARNUNG: (x)”

Diese Makros erwarten als letzten Parameter den Wert, den die Funktion, in der man sich ge-rade befindet, zurückgeben soll (im Falle eines Fehlers wird eine Funktion für gewöhnlich ver-lassen). Hinter den oben gezeigten Texten stehen immer noch die Datei, in der das Makro auf-gerufen wurde (z.B. TRIBASE.CPP), die Zeilennummer und die Funktion, in der es geschah.

Beim Bezeichner TB_ERROR besteht eine kleine Verwechselungsgefahr, denn TB_ERROR ist sowohl ein Element der tbResult-Aufzählung als auch ein Makro, welches einen Fehlertext ins Logbuch schreibt und danach die aktuelle Funktion mit einem angegebenen Rückgabewert abbricht.

Des Weiteren bietet das Makro TB_ERROR_DIRECTX einen einfachen Weg, einen DirectX-Fehlercode in einen beschreibenden Text umzuwandeln. Das Makro erwartet drei Parameter:

zuerst den Namen der DirectX-Funktion oder -Methode, die den Fehler verursacht hat (als String), dann ihren HRESULT-Rückgabewert und zum Schluss – wie auch die anderen Makros – den Rückgabewert für die aktuelle Funktion. Bevor Sie das Makro anwenden, prüfen Sie mit

FAILED, ob die DirectX-Funktion fehlgeschlagen ist. Es folgt ein Beispiel.

tbResult TestFunction() {

HRESULT hResult; // DirectX-Rückgabewert

if(FAILED(hResult = pDirectXInterface->Method())) {

// Die Methode wurde nicht fehlerfrei ausgeführt! Der Fehlercode wird nun in einen // Text umgewandelt und ins Logbuch geschrieben. Danach wird die Funktion mit dem // Code TB_ERROR verlassen.

TB_ERROR_DIRECTX("pDirectXInterface->Method()", hResult, TB_ERROR);

}

// Die Methode wurde korrekt ausgeführt. Informationstext schreiben.

TB_INFO("Die DirectX-Methode wurde erfolgreich ausgeführt! Suuuuuper!“);

// Alles OK!

return TB_OK;

}

Listing 1.18 Beispiel für das TB_ERROR_DIRECTX-Makro

Im Falle eines Fehlers stünde später beispielsweise im Logbuch: „Der Aufruf von

pDirectXInterface->Method verursachte den DirectX-Fehler E_FAIL! Beschreibung: The method failed. (TEST.CPP, Zeile 11, Funktion TestFunction)“.

„The method failed.“ ist dabei der wahnsinnig hilfreiche „beschreibende Text“, den die Hilfs-funktion DXGetErrorDescription9 geliefert hat. Viel mehr darf man leider auch meistens nicht erwarten.

1.4.6 Rückblick

ƒ Die in diesem Buch entwickelten Programme und Spiele greifen auf die TriBase-Engine zurück. Dabei handelt es sich um eine Engine, die vor allem den Umgang mit DirectX er-leichtert.

ƒ Programme, die mit der Engine arbeiten sollen, müssen mit der Datei TRIBASE.LIB (Re-lease) oder TRIBASED.LIB (Debug) gelinkt werden und die Datei TRIBASE.H einbinden.

ƒ In diesem Abschnitt wurden die wichtigsten grundlegenden Grundsätze und Funktionen der Engine erläutert. Dazu gehören die HTML-Logbuchfunktionen, die Speicherverwal-tung, Funktionen für virtuelle Dateien und Zufallsfunktionen sowie einige nützliche Mak-ros.

1.4.7 Übungsaufgaben

Von nun an wird es nach jedem größeren Abschnitt ein paar Übungsaufgaben geben. Es ist natürlich nicht verpflichtend, diese Aufgaben zu lösen. Aber wenn man es schafft, bestätigt man sich dadurch natürlich selbst, dass man alles verstanden hat. Auf eine Veröffentlichung von „Musterlösungen“ verzichte ich. Wenn Sie glauben, eine Aufgabe gut gelöst zu haben, können Sie mir Ihr Programm gerne schicken (david_scherfgen@gmx.de), und ich werde es dann auf meiner Internetseite (http://www.spieleprogrammierer.de) veröffentlichen.

Sie sollten sich die Aufgaben zumindest immer durchlesen, denn vor allem in späteren Kapi-teln beinhalten sie viele hilfreiche Tipps und Tricks, auf die man normalerweise so schnell nicht kommt.

Tipps zu den Aufgaben sind in sehr heller und kleiner Schrift gedruckt, so dass Sie sie nicht lesen müssen, wenn Sie nicht möchten.

1. Schreiben Sie ein Programm, das die TriBase-Engine initialisiert, einen kleinen String oder ein paar Zahlen ins Logbuch schreibt und die Engine dann wieder herunterfährt. Achten Sie darauf, dass das Programm sowohl im Debug- als auch im Release-Modus funktioniert und auch die richtigen Bibliotheksdateien verwendet. Sie sollten sich stets solch ein Visual C++-Projekt gespeichert halten, um nicht bei jedem Mal wieder alle Einstellungen vor-nehmen zu müssen.

2. Ein Programm soll zwei Speicherbereiche reservieren, in die jeweils 1000 int-Werte pas-sen. Diese Speicherbereiche werden nun mit Zufallszahlen gefüllt. Anschließend soll der Inhalt der beiden Speicherbereiche vertauscht werden.

Tipp: Dritten Speicherbereich als Puffer verwenden und den Kopiervorgang mitmemcpy erledigen!

3. Programmieren Sie einen kleinen „Unzipper“. Dies kann ein Konsolenprogramm sein, das den Benutzer auffordert, den Namen einer (unkomprimierten) Zip-Datei und möglicher-weise ein Passwort einzugeben. Weiterhin soll der Benutzer den Namen der Datei im Ar-chiv angeben, die „entpackt“ werden soll. Das Programm soll genau dies tun.

Tipp: Verwenden Sie tbVFile, und speichern Sie die Datei mit tbVFile::SaveToFile.