Registriert: Mi Nov 18, 2009 10:55 Beiträge: 20
Programmiersprache: C++
Hallo. Ich bin seit gestern dabei mir Konzepte für die Umsetzung einer Map-Klasse für ein Spiel zu überlegen. Das Problem ist, dass ich nicht weiß, wie ich es am besten umsetzte. Also die Kamera soll isometrisch über der Karte angebracht sein und man soll sie drehen - das ist ja auch nicht das Problem. Aber die Karte soll in gleichgroße Felder unterteilt werden. Es folgen jetzt einige Bilder, an denen die Eingeschaften der Karte erklärt werden sollen. Dieses Bild zeigt erstmal nur die Unterteilung in Felder, die rote Auswahl ist hierbei ein (besetztes) Feld. Auf einem besetzten Feld steht (logischerweise) irgendetwas. Hierzu wollte ich ein 3D-Objekt-Klassen-Objekt aus einer Liste laden und an die richtigen Weltkoordinaten setzten. Diese 3DOKOs haben als membervariable einen const char* Dateinamen, dessen inhalt geladen und als mesh-Datei angegeben werden kann. Zusätzlich soll die 3DOK noch verschiedene Werte haben wie Ausrichtung (Nord/Süd/West/Ost), damit wird das Mesh entweder garnicht, um 90°, um 180° oder um -90° gedreht. Ein anderer Wert wäre die Position im Raum z.B. 10x6, das wäre dann halt das Feld an den Koordinaten für das 10x6te Feldobjekt der Karte. Nun haben wir also sagen wir ein solches Objekt, z.B. einen Tisch gesetzt. Der Tisch ist allerdings so groß wie 2 Felder, nun müsste die Map also eine Wahrnehmung bekommen, und der Tisch der Map übermitteln, dass 2 Felder besetzt sind, wobei das zweite Feld im normalfall nördlich des Anfangsfeldes ist. Wird der Tisch dann nach süd/west/ost gedreht, muss das 2. besetzte Feld ermittelt werden.
Doch das eigentliche Problem, bei dem ganzen werden die Wände/Türfelder/Räume sein. Diese sehen nämlich so aus. Die roten Linien stellen Mauern dar und die grüne Linie ist eine anfang noch geschlossene Tür.
Habt ihr vielleicht Tipps oder am besten Codeschnippsel, die mir helfen können damit klar zu kommen. Dann ist da noch die Frage des Desings: Sollte ich die Mauern in OpenGL als primitive "hochziehen", nachdem bekannt ist, wo sie stehen sollen - oder soll ich einige Mauer-Meshes haben und diese Plazieren? Dann zu den Türen, was auch ein Problem sein könnte: Sollte ich hier eine Textur verwenden, die im "Tür-ist-offen"-Modus anders aussieht, als im "Tür-ist-zu"-Modus? Oder Soll ich an den Türstellen einfach keine Mauer setzen, sondern ein Türobjekt, das eine "Aufschwungs-Seite" besitzt, die sich bewegt während die andere immer still steht?
Ich hab mal gerade 15 Minuten invertiert und mir was überlegt. Das folgende ist UML-Notation mit C++, also wenn etwas unklar ist einfach fragen.
Dateianhang:
uml.png
Ein Mesh ist einfach nur eine Klasse die irgendwie Geometrie kapselt und rendern kann. Angenommen du hast einen Stuhl oder ähnliches mehrfach in deiner Map, so gäbe es nur ein Mesh-Objekt mit der Geometrie des Stuhls. Für jeden Stuhl in deiner Map gibt es aber dann ein MapObject, welches die Position/Ausrichtung [3] sowie einen Pointer auf das Mesh enthält. Ein Aufruf von MapObject.render() macht nichts weiter als die Position/Ausrichtung an OpenGL zu übergeben (glTranslate, glRotate, ...) und dann mesh->render() aufzurufen. Da es nur vier verschiedene Ausrichtungen gibt habe ich das mal als Enumeration [2] realisiert, man könnte hier aber auch einen Winkel nehmen.
Das zentrale Objekt ist das "Map" Singleton [1], es kennt alle Objekte auf deiner Karte. Deine Map besteht aus n mal n Feldern. Es gibt drei große Arrays die deine Map darstellen:
field : Pointer auf das Objekt das auf dem jeweiligen Feld steht
wallsNorthSouth : Pointer auf die Wand-Objekt die in Nord-Süd-Richtung verlaufen. Da die Map n Felder breit ist gibt es n+1 Wände dazwischen.
wallsWestEast : Wände in West-Ost-Richtung
Wenn es großes Objekt (dein Beispiel war ein Tisch) mehrere Felder bedeckt zeigen einfach alle Pointer auf das selbe MapObject. Aus diesem Grund ist auch die Position im MapObject gespeichert, ob wohl sich das aus der Array-Positon berechnen ließe.
Da MapObjecte mehrfach verlinkt sein könnten, gibt es die Methode updateVisibleList(). Diese findet zuerst alle Objekte im Sichtbereich der Kamera und eliminiert dann mehrfach vorkommende Objekte. Das geht am einfachsten indem man die Liste der Pointer zuerst sortiert und dann testet ob an einer Stelle zweimal hintereinander der gleiche Pointer zu finden ist. Die Methode updateVisibleList() muss nur dann aufgerufen werden wenn sich die Objekte und/oder die Kameraansicht verändert haben.
Mit Map.render() ruft man dann einfach die Methode render() aller Objekte in der Visible-Liste auf.
[Edit]
Zitat:
Sollte ich hier eine Textur verwenden, die im "Tür-ist-offen"-Modus anders aussieht, als im "Tür-ist-zu"-Modus? Oder Soll ich an den Türstellen einfach keine Mauer setzen, sondern ein Türobjekt, das eine "Aufschwungs-Seite" besitzt, die sich bewegt während die andere immer still steht?
Die Frage ist mir nicht ganz klar. Natürlich kannst du dem MapObject noch etwas mehr Funktionalität geben, z.B. eine Klasse DoorObject die von MapObject abgeleitet wird. Dieses ist dann in der Lage einen statischen Türrahmen sowie eine darin eingehängte bewegliche Tür zu rendern. Wenn dir Auf/Zu reicht gibt es einfach nur zwei Stellungen für die Tür. Wenn die Tür wirklich animiert aufgehen soll wird es komplizierter. [/Edit]
[1] Singleton ist eine Bezeichnung für ein sogenanntes Design-Pattern bei dem von einer Klasse nur eine einzige Instanz (Objekt) erzeugt wird. Dies stellt man durch einen privaten Konstruktor sicher. Ein Pointer auf diese einzige Instanz wird in einem statischen Attribut gehalten, welches man mit einer ebenfalls statischen Methode (z.B. getInstance) zugreifen kann. Die Instanz wird erzeugt, wenn die statische Methode das erste mal aufgerufen wird.
[2] Ich weiß nicht ob es in Delphi Enums gibt, letztlich ist das aber nur ein Integer den man bequem mit Namen ansprechen kann.
[3] Im Bild besteht die Position aus einem Vector3....das soll ein Record sein der x,y und z Koordinaten enthält. Sorry, war Gewohnheit...du brauchst natürlich nur zwei Koordinaten. Ich hatte keine Lust das Bild neu zu machen.
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.
Registriert: Mi Nov 18, 2009 10:55 Beiträge: 20
Programmiersprache: C++
Besten Dank, Coolcat. Leider stecke ich schon am Anfang fest und ich schätze, dass es diesmal an rudimentären C++-Kentnissen hapert. Also ich versuche gerade den etwas zum Laden eines Meshes zu schreiben. Das Ganze ist noch sehr einfach und ist noch auf kein Datei-Format (außer eventuell mein eigenes) angepasst, aber hier erstmal, wieweit ich damit bin:
Gibt es jemanden, der mir sagen kann, was um alles in der Projektionsmatrix da schief läuft? Ich vermute, dass die Strukturen da einfach nicht mitmachen wollen, - aber es kann doch nicht sein, dass ich nichtmal abfragen kann, wie groß Meshes ist...
Naja wie auch immer, ich bedanke mich jetzt erstmal bei Coolcat. Ich denke damit komme ich erstmal klar. Übrigens wollte ich noch kurz anmerken, dass mir bereits klar war, dass ich jedes Mesh nur einmal zu laden brauche. (Sowie die meisten verwendeten Fachbegriffe - aber es gab ganzklar auch neues, was ich vorher nicht gekannt habe )
Tut mir Leid, dass ich noch so neu bin und mich so unwissend benehme - aber das bin ich ja noch... (Programmiere nur privat.)
Registriert: Mi Jan 31, 2007 18:32 Beiträge: 150
Programmiersprache: Pascal
Nur ein Schnellschussmeinerseits, aber hast du vll vergessen etwas zu initialisieren? Meshes würde sich da als guter Kanidat anbieten. Ich nehme mal an es handelt sich dabeium irgendeine Art List-Klasse... Vermutung ist nun das das ganze and der Stelle Meshes.Empty() abstürtzt.
Nur mal so am Rande besitzt die IDE mit der du arbeitest einen Debugger? wenn ja wäre diesen hier zu benutzen kein schlechter Anfang da du direkt herausfinden kannst in welcher Zeile das ganze nicht mehr will.(macht das finden von Fehlern durchaus einfacher^^)
Vermutung ist nun das das ganze and der Stelle Meshes.Empty() abstürtzt.
Also das kann eigentlich nicht sein, da Meshes kein Pointer ist. Aber man weiß ja nie
Es kann sein das die Standardausgabe nicht vollständig ausgegeben wurde. Da hilft ein explizites std::cout.flush(). Ich benutze gerne dieses nette Tierchen:
Du schreibst einfach in eine Zeile "DEBUG_LINE;" und der gibt dir Dateiname und Zeilennummer aus, mit der Garantie das es auch wirklich in der Standardausgabe angekommen ist sobald das DEBUG_LINE ausgeführt wurde. DEBUG_GL_ERROR ist sowas ähnliches, fragt aber OpenGL-Fehlermeldungen ab. Über ein #define DEBUG bzw. #undef DEBUG vor dem inkludieren des Headers kannst du steuern ob du die Debugausgabe nutzen willst oder nicht. Bei ausgeschalteter Debug-Ausgabe wird der Debug-Code vom Compiler entfernt, es gibt also keinen Performance-Nachteil. Möglicherweise musst du das ganze von "std::cout" auf dein "Infos" umbauen.
Meshes.push_back(...) ist auskommentiert, darum ist der std::vector leer und size() somit 0. Der Fehler kommt dann beim Zugriff auf Meshes[Meshes.size()-1]. Die Ausgabe "Leer" siehst du nicht, weil eben das genannte flush() fehlt.
Der Aufruf von reserve() macht übrigens nur dann Sinn, wenn du mehrere (viele) Objekte hinzufügen willst und du im voraus genau weißt wie viele es werden. Passt bei einem push_back() das Objekt nicht mehr in den Vektor, vergrößert sich der Vektor automatisch um einen gewissen Prozentsatz (z.B. 50%).
Edit: Mist...das ist es nicht. std::endl macht ja ein flush...
Registriert: Mi Nov 18, 2009 10:55 Beiträge: 20
Programmiersprache: C++
Hmmm das ist komisch - es stürzt tatsächlich genau beim Ansprechen des Vektors "Mehses" ab. Es ist egal, ob ich eine Abfrage wie z.B. if(Meshes.empty(), oder andere Funktionen des Vektors aufrufe ab: Es ist, als würde er außerhalb des Speichers stehen
Beide Zeilen verursachen einen totalabsturz des Programmes (Reihenfolge der Befehle ist irrelevant).
Währned ich diesen Post verfasse überprüfe ich noch mehrere Fehlerquellen: 1) Ich habe mal aus s_Mesh den Vektor für s_Material entfernt - Absturz an der gleichen Stelle 2) Auflösung vom "Points"-Vektor in dies
Ergebnis: Keine Besserung. 3) Ich habe einfach mal (ohne weiter damit zu arbeiten) den Befehl Meshes.size(); verwendet - das wird ohne Probleme geschluckt...
So und zum Abschluss möchte ich festhalten: Meine Welt steht gerade Kopf.
P.S.: Einen Loader werde ich wohl auch letztendlich nehmen, aber Übung schadet nie. ^^' Außerdem mag ich es (noch) nicht sehr fremde Arbeit zu benutzen (außer wirklich wichtige Sachen wie Fenster-Erzeugung, OpenGL, und Sounds).
Registriert: Mi Nov 18, 2009 10:55 Beiträge: 20
Programmiersprache: C++
Vielleicht habe ich mich da etwas unklar ausgedrückt, habe dem Code ab Zeile 201 aber etwas zur Verdeutlichung hinzugefügt, was ich mit Meshes.size(); gemeint habe.
s_Mesh *NeuesMesh; class c_MeshLoader { private: ///std::vector <s_Mesh*> Meshes;//! Sören... Das, was du da machst, ist BÖSE! std::vector <s_Mesh> Meshes; public: //2 Funktionen: Eine zum Laden eines Meshes über den Dateinamen und eine andere zum Auswerten der Mesh-Dateidaten. Bei Erfolgreichem Laden+Auswerten würde ein 'currentMesh' über 'new' erzeugt, mit den ausgewerteten Werten befüllt und in den Vektor geschoben wird. ('delete nicht vergessen') inline s_Mesh &get_Mesh(const int ID) { DEBUG_OUT << "[c_MeshLoader]Get Mesh with ID '" << ID << "'." << std::endl; DEBUG_FOUT << "[c_MeshLoader]Get Mesh with ID '" << ID << "'." << std::endl; return Meshes[ID]; } void load_Mesh(const char* Name); };
bool schrott(false); //Diese Funktion kann noch kein Mesh verarbeiten. (Es wird noch garnicht ausgelesen, die Datei sollte aber vorhanden sein) void c_MeshLoader::load_Mesh(const char* Name) { std::ifstream File;File.open(Name, std::ios::in); if(File.good()) { DEBUG_OUT << "[c_MeshLoader]Read File '" << Name << "'." << std::endl; DEBUG_FOUT << "[c_MeshLoader]Read File '" << Name << "'." << std::endl; s_Mesh *currentMesh = new s_Mesh(); //s_Mesh currentMesh; ///File.close(); ///File.open(Name); while(!schrott/*File.eof()*/) { DEBUG_OUT << "[c_MeshLoader]..." << std::endl; DEBUG_FOUT << "[c_MeshLoader]..." << std::endl; if(!schrott) { currentMesh->createAndAddPoint(-50.f, -50.f, -50.f); currentMesh->createAndAddPoint(-50.f, 50.f, -50.f); currentMesh->createAndAddPoint( 50.f, 50.f, -50.f); currentMesh->createAndAddPoint( 50.f, -50.f, -50.f); schrott=true; } } File.close(); DEBUG_OUT << "[c_MeshLoader]Reading completed." << std::endl; DEBUG_FOUT << "[c_MeshLoader]Reading completed." << std::endl;
//class LifeformMapObject; //Hier erstmal total unnötig class DeadMapObject { private: //Membervariablen wie z.B. einen Funktionszeiger auf die Funktion für die "KI" bzw. "Perception" des Objektes [an ID angepasst] double Coordinate_X; double Coordinate_Y; orientation Orientation; //std::vector <double> Coordinates; //STL-Vektoren kann man schlecht/garnicht über Initialisierungslisten mit x [in diesem Fall 2] reservieren. public: //Getter-Funktionen //Setter-Funktionen [für "künstliche" Positionsveränderungen während des Spieles] double extract_MeshXPos(); //Übermittelt X-Position auf der Karte double extract_MeshYPos(); orientation extract_MeshOrientation(); double get_XPosition(); DeadMapObject(const int ID): Coordinate_X(extract_MeshXPos()), Coordinate_Y(extract_MeshYPos()), Orientation(extract_MeshOrientation()) {}; //Vielleicht etwas ungeschickt formuliert, aber es "dürfte" inetwa so funktionieren. void draw_Object(); //Zeichnet das Objekt und zusätzlich ruft die Funktion noch den Funktionspointer auf für Veränderungen an der Orientierung und der Position des Objektes. };
double DeadMapObject::extract_MeshXPos() { ///MeshMan->get_Mesh(ID); //usw. usf. } orientation DeadMapObject::extract_MeshOrientation() { //orientation *Neue = new orientation::N; return ((orientation)0); //0 für N //usw. usf. }
// Start game loop while (App.IsOpened()) { // Process events sf::Event Event; while (App.GetEvent(Event)) { // Close window : exit if (Event.Type == sf::Event::Closed) App.Close();
// Set the active window before using OpenGL commands // It's useless here because active window is always the same, // but don't forget it if you use multiple windows or controls App.SetActive();
// Clear color and depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Apply some transformations glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.f, 0.f, -200.f);
*Hust, es ist ratsam pro Klasse eine Header-Datei und eine Cpp-Datei zu haben. Nur bei ganz einfachen Klassen oder Klassen die extrem eng zusammen arbeiten kannst du mehrere in einem zusammenfassen.
Die globale Variable MeshMan wird niemals initialisiert, wenn ich das richtig überblicke. Deine load_Mesh-Methode funktioniert genau so lange bis du den "this"-Pointer auf die Objektinstanz benötigst....das Attribut Meshes wird niemals erzeugt. Deine "Normalen Funktionsaufrufe" werden vom Compiler wegoptimiert, da sie das Objekt Meshes nicht verändern (Die Methoden sind in std::vector sicherlich als const markiert).
Genau aus diesem Grunde verwendet man für solche Dinge das oben erwähnte Singleton-Pattern. Dort wird sichergestellt das der Konstruktor genau einmal ausgeführt wird. So ein Singleton könnte z.B. wie folgt aussehen:
MySingleton::~MySingleton() { /* Achtung: Wird niemals aufgerufen! Speicher wird beim beenden des Programms aber sowieso komplett freigegeben. Aufpassen aber bei externen Dingen wie Dateien oder Datenbankverbindungen. */ }
Benutzen würdest du das so:
Code:
MySingleton::instance()->methode(a,b,c);
Wenn du mehrere Methoden hinter einander aufrufst kann es natürlich Sinn machen sich lokal (oder als Klassenattribut) einen Pointer anzulegen:
Mitglieder in diesem Forum: Bing [Bot] und 9 Gäste
Du darfst keine neuen Themen in diesem Forum erstellen. Du darfst keine Antworten zu Themen in diesem Forum erstellen. Du darfst deine Beiträge in diesem Forum nicht ändern. Du darfst deine Beiträge in diesem Forum nicht löschen. Du darfst keine Dateianhänge in diesem Forum erstellen.