190 likes | 360 Views
Datenstrukturen. Look-Up Tabellen, Zufallszahlen, Listen, Speichermanagement und Dateiverwaltung. Look-Up Tabellen: Probleme bei Spielen. Spielszenen müssen sehr oft pro Sekunden berechnet werden Wenn es zu viele Berechnungen werden fängt das Spiel an zu „ruckeln“
E N D
Datenstrukturen Look-Up Tabellen, Zufallszahlen, Listen, Speichermanagement und Dateiverwaltung
Look-Up Tabellen: Probleme bei Spielen • Spielszenen müssen sehr oft pro Sekunden berechnet werden • Wenn es zu viele Berechnungen werden fängt das Spiel an zu „ruckeln“ • Problem ist nicht immer die Grafikkarte (bei 3D spielen) • Häufig ist KI schuld (z.B. bei Strategiespielen: viele Einheiten werden von Computer bewegt und kommandiert) wiederkehrende Operationen werden immer neu berechnet
Lösung: Look Up Tabelle • Wikipedia: • In der Informatik ist eine Look-Up-Table eine Datenstruktur, meist ein (assoziatives) Array, das komplizierte Laufzeitberechnungen durch einen einfachen Zugriff auf die Datenstruktur ersetzt. Dies führt zu einem signifikanten Geschwindigkeitsgewinn, sofern die benötigten Speicherzugriffe schneller sind als die normale Berechnung. • Alle wiederkehrenden Berechnungen werden durch Look-Up Tabellen vermieden
Was macht eine Look-Up Tabelle? • Vor Beginn des Spiels werden alle benötigten Werte vorberechnet • Es gibt verschiedene Typen von Look-Up Tabellen, so dass man sich vorher überlegen muss, von welcher Art (sin, cos, Quadratwurzel) diese sein muss und welche Genauigkeit diese haben muss • Es können nicht nur Funktionswerte, sondern bsplw. auch Zufallszahlen in Look-Up Tabellen gespeichert werden • Um flexibel mit Look Up Tabellen arbeiten zu können sollte man sie in Klassen einbetten
Beispiel: • #include <iostream.h> • #include <math.h> • #define SAFE_DELETE(p) {if(p) {delete (p); (p)=NULL;}} • #define SAFE_DELETE_ARRAY(p) {if(p) {delete[] (p); (p)=NULL;}} • // bis hier hin nur „Vorgeplänkel“, Includen von iostream und math-Klasse und Einbinden von Makros von Heap-Speicher • //Beispiel: sinus-Tabelle
class CSinLookUp • { • private: • long AnzElements; • float Genauigkeit; • float* Tabelle;
public: • CSinLookUp(float Schrittweite = 1.0f) // Konstruktor • { • Genauigkeit = 1.0f/Schrittweite; • AnzElements = (long)(360*Genauigkeit); • Tabelle = new float[AnzElements]; • for(long winkel = 0; winkel < AnzElements; winkel++) • { • // Berechnung der Sinus Look-Up Tabelle • Tabelle[winkel] = sinf(winkel*Schrittweite*3.141592654f/180.0f); • } • } • ~CSinLookUp() // Destruktor • { • SAFE_DELETE_ARRAY(Tabelle) // Zerstören des Arrays • } • float ReturnSinValue(float winkel) // Zugriffsfunktion • { • if(winkel < 0.0f) • winkel += 360.0f; • else if(winkel > 360.0f) • winkel -= 360.0f; • return(Tabelle[(long)(winkel*Genauigkeit)]); • } • };
Aufruf in Methode mit: • CSinLookUp* SinLookUp = new CSinLookUp(0.1f); // Schrittweite //von 0.1 • cout << "sin(45 DEG) = " << SinLookUp->ReturnSinValue(45.0f) << endl; • SAFE_DELETE(SinLookUp) // zerstören der Tabelle
Zufallszahlen • Werden in einem Spiel hauptsächlich für die Variation des Spielablaufes und des Gegnerverhaltens eingesetzt • In C++ gibt es eine Funktion für Zufallszahlen • rand()-Funktion ist komfortabel für die Erzeugung von Zufallszahlen • Am komfortabelsten ist es, eine Klasse alleine für Zufallszahlen anzulegen und die Zahlen in einer Tabelle zu speichern
Aufruf der Zufallszahlenmethode und Initialisierung des „Zufallsgenerators“ • int main(void) • { • // Initialisierung des Zufallsgenerators • srand(time(0)); • // dann Aufruf der Zufallszahlenfunktion • cout << frnd(-5.0f,5.0f) << endl; • }
Erzeugung von Zufallszahlen mit rand()-Funktionen • Mit einfachen Inline-Funktion (hier float-Zufallszahl): • float tempFrnd; • inline float frnd(float low, float high) • { • tempFrnd = low + ( high - low ) * ( (long)rand() ) / RAND_MAX; • // RAND_MAX enthält die größtmögliche Zufallszahl • if(tempFrnd < high) • return tempFrnd; • else • return low; • } • // weiteres Beispiel im Buch (extra Klasse für Zufallszahlen)
Einfach verkettete Listen • Einheit zur Speicherung und Verwaltung relevanter Daten • Besteht aus mehreren Knoten (sofern mehrere Daten vorhanden sind) • Jeder Knoten ist über einen Zeiger mit dem nächsten Knoten verbunden • Jeder Knoten ist entweder eine Strukturvariable oder ein Klassenobjekt • Erster Knoten = Head-Knoten • Letzter Knoten = Tail-Knoten
Wie sieht eine einfach verkettete Liste aus? • Head Node Node Node Tail
Probleme • Probleme bei Listen • Sehr unflexibel • Sehr langsam • Will man auf ein Element zugreifen, muss die gesamte Liste durchlaufen werden um das Element zu finden (es sei denn es befindet sich am Anfang) • Die Reihenfolge der abgelegten Objekte entspricht meistens nicht der Reihenfolge der im Spiel benötigten Objekte
Speicherprobleme • Jedes Erzeugen von Knoten kostet Speicherplatz (mit „new“ wird Speicherplatz reserviert) • „new“ sucht nach einem Speicherblock mit geeigneter Größe • Nur wenn ein geeigneter Block gefunden wird kann Objekt angelegt werden • Sind alle gefundenen Speicherblöcke zu klein, kann Objekt nicht erzeugt werden Speicherfragmentierung
Arrays lösen diese Probleme • Arrays reservieren einen bestimmten Speicherplatz • Kritik: unnötiger Speicherplatz wird verschenkt und Arrays sind zu unflexibel • Für Spieleprogrammierung gilt das nicht • Beispiel: • Spiel mit animierten Asteroiden • Obergrenze der Anzahl an Asterioden wird durch vordefiniertes Array festgelegt • Kein Nachteil, weil sowieso nur eine gewisse Anzahl gerendert und animiert werden können • Zugriff auf Array geht viel schneller als Zugriff auf Element in verketteter Liste
Memory Manager • Es ist sinnvoll eine Klasse zu entwerfen die die Speicherverwaltung in einem Spiel übernimmt • Also alles in einer Memory Manager Klasse • Klasse hat vordefiniertes Array mit dem gearbeitet werden kann • Beispiel im Buch • Programmbeispiele Tag2 Memory Manager
Dateiverwaltung • Dazu zählt etwa eine Speicherfunktion für ein Spiel • Wichtige Struktur in Beispiel: • _finddata_t Speichert alle Informationen über die Dateiarbeit • Wichtige Funktionen sind: • _findfirst( "Szenarien/*.*", &c_file ) • Untersucht ob der Ordner überhaupt existiert • _findnext( hFile, &c_file ) • Alle weiteren Dateien werden durchsucht