Tutorials - Programmieren eines einfachen 2D Leveleditors
Sprachenübersicht/Programmierung/C / C++/ C#/Spieleprogrammierung
Programmieren eines einfachen 2D Leveleditors
Diese Seite wurde 9287 mal aufgerufen.
Dieser Artikel wurde in einem Wikiweb System geschrieben, das heißt, Sie können die Artikel jederzeit editieren, wenn Sie einen Fehler gefunden haben, oder etwas hinzufügen wollen.
Editieren Versionen Linkpartnerschaft Bottom Printversion
Keywords: Leveleditor programmieren, 2D, DirectX, Level speichern, laden, stream, struktur, map, engine
Inhaltsverzeichnis
In diesem Tutorial wird gezeigt wie man die Grundlagen für einen einfachen Leveleditor für eine auf Tile basierende Engine schafft.
Dafür schreiben wir eine Klasse, mit der man Felder verändern, und speichern kann.
Zuerst werden schreiben wir einen Header in die Datei, der so aussieht:
Code:
struct LevelData
{
unsigned int iMapID; //Speichert die ID der map
unsigned int iWidth; //Speichert die Breite der map
unsigned int iHeight; //Speichert die Anzahl der Felder in der Höhe
};
Die Adresse dieser Struktur wird der Klasse dann über einen Pointer gegeben, damit auf die Struktur intern & extern zugegriffen werden kann (und auch von anderen Klassen)
Danach schreiben wir der Höhe * Breite Felder in die Datei, unserer Struktur sieht so aus:
Code:
struct MapField
{
char* strBitmapName;
TeleportField Teleport;
};
Damit wird der Name der Tile (ein Bitmap eines einzelnen Feldes) Datei, und eine Struktur, wohin man sich Teleportiert gespeichert.
Wenn iMapID der Struktur auf 0 ist, dann kann man sich mit diesem Feld einfach nicht teleportieren.
Die Variable iTeleporttype könnte für den Typ der Teleportation (Levelübergang, ...) verwendet werden.
Code:
struct TeleportField
{
int iMapID;
int iTargetWidth;
int iTargetHeight;
int iTeleporttype;
};
Das Grundgerüst schaut dann so aus:
Code:
//This class manages the 2D Levels
class Level
{
public:
//Ruft die 2. Funktion auf, mit weniger Informationen
void Init(LevelData *pLevelData)
{
}
//Diese Funktion initialisiert die Klasse, dabei wird ein Pointer zu den Headerinformationen übergeben
void Init(LevelData *pLevelData,std::string strStandardFilename,int iWidth, int iHeight)
{
}
//Diese Funktion ändert die Größe der map, ohne die Felder zu verändern
void SetSizeOfMap(int iWidth, int iHeight)
{
}
//Hier wird eine komplett neue map mit dem Standarddateinamen erstellt
void MakeNewMap(int iWidth, int iHeight,std::string strStandardFilename)
{
}
//Gibt ein Feld zurück
MapField* GetField(int iWidth, int iHeight)
{
}
//Speichert das Level
int SaveLvl(std::string strFilename)
{
}
//Ladet das Level
void LoadLvl(std::string strFilename)
{
}
private:
//This struct saves the header of the file
LevelData *m_pLevelDatas;
};
Die ersten zwei Funktionen sehen so aus:
Code:
//Initialize the class
void Init(LevelData *pLevelData)
{
this->Init(pLevelData,"",0,0);
}
//Initialize the class
void Init(LevelData *pLevelData,std::string strStandardFilename,int iWidth, int iHeight)
{
m_pLevelDatas = pLevelData;
m_pLevelDatas->iWidth = iWidth;
m_pLevelDatas->iHeight = iHeight;
if(iWidth && iHeight)
this->MakeNewMap(iWidth,iHeight,pStandardFilename);
}
Diese Funktionen initialisieren die Klasse, dabei wird zuerst der Pointer zu den Headerinformationen gesetzt.
Danach wird die Höhe und Breite der Map gesetzt, wenn die Größe angegeben wird, dann wird gleich darauf eine neue map erstellt.
Jetzt brauchen wir noch ein 2 dimensionales Feld, welches die Felder speichert.
Dafür benutzen wir eine stl vector in einem vector für die Mapfield Struktur:
Code:
vector < vector <MapField> > m_MapField;
Die Leerzeichen zwischen den <> sind wichtig, wenn man es anderst macht gibt es Probleme.
Auf den Vektor kann man dan problemlos per m_MapField[x][y] zugreifen, und er wird auch automatisch per Destruktor gelöscht.
Code:
//Reserviert iWidth Elemente
m_MapField.resize(m_pLevelDatas->iWidth);
//Geht alle Elemente durch und reserviert für jedes iHeight Elemente
for(int i = 0;i < m_pLevelDatas->iWidth;i++)
{
//Reserviert iHeight Elemente
m_MapField[i].resize(m_pLevelDatas->iHeight);
//Setzt alle auf den Standard
for(int z = 0;z < m_pLevelDatas->iHeight;z++)
{
m_MapField[i][z].strBitmapName = (char*)strStandardFilename;
m_MapField[i][z].Teleport.iMapID = 0;
}
}
Mit
Code:
//Resize it an sets it 0
for(int i = 0;i < m_pLevelDatas->iWidth;i++)
m_MapField[i].clear();
m_MapField.clear();
löschen wir die alten Daten.
Dies MakeNewMap Funktion sieht dan so aus:
Code:
//Makes a new map (deletes all old datas)
void MakeNewMap(int iWidth, int iHeight,std::string strStandardFilename)
{
m_pLevelDatas->iWidth = iWidth;
m_pLevelDatas->iHeight = iHeight;
//Clears it if there is already a version
if(!m_bMap)
m_bMap = true;
else
{
//Resize it an sets it 0
for(int i = 0;i < m_pLevelDatas->iWidth;i++)
m_MapField[i].clear();
m_MapField.clear();
}
//Creats iWidth elements
m_MapField.resize(m_pLevelDatas->iWidth);
//Creates for each iWidth element iHeight elements
for(int i = 0;i < m_pLevelDatas->iWidth;i++)
{
m_MapField[i].resize(m_pLevelDatas->iHeight);
for(int z = 0;z < m_pLevelDatas->iHeight;z++)
{
//Sets it 0/pStandardFilename
m_MapField[i][z].strBitmapName = (char*)strStandardFilename;
m_MapField[i][z].Teleport.iMapID = 0;
}
}
}
Jetzt brauchen wir noch eine Funktion um die Felder auszulesen, und sie verändern zu können.
Code:
//Returns the Field
MapField* GetField(int iX, int iY)
{
if ((iX > m_pLevelDatas->iWidth) || (iY > iHeight))
{
std::cout << "Error, field doesn't exist." << std::endl;
}
return &m_MapField[iX-1][iY-1];
}
Damit kann man mit Klasse.GetField(1,3)->pBitmapName = "test.bmp"; bequem die Feld Struktur verändern, oder sie mit std::cout << Klasse.GetField(1,3)->pBitmapName << std::endl; auslesen.
Jetzt brauchen wir nur noch eine Funktin zum speichern, zum laden, und um die Größe der map zu verändern.
Wir benutzen zum speichern streams, einen ifstream (input stream) und einen ofstream (output stream).
Dafür benötigen wir den Header #include <fstream>
Mit fStream.open(pFilename,std::fstream::out | std::fstream::binary); öffnen wir die Datei mit den flags output, und binary, damit kann man in die Datei binär (es wird so gespeichert wie es der Compiler sieht) schreiben.
Danach speichern wir die Headerinformationen, und dann nach und nach die Felder.
Code:
//Saves the level
int SaveLvl(std:string strFilename)
{
if(!m_bMap)
return 1;
std::ofstream ofStream;
//Opens the file binary
ofStream.open(strFilename.c_str(),fstream::out | fstream::binary);
//Writes the LevelData
ofStream.write((char*)m_pLevelDatas, sizeof(*m_pLevelDatas));
//Writes all Fields into the file
for(int z = 0;z < m_pLevelDatas->iWidth;z++)
for(int i = 0;i < m_pLevelDatas->iHeight;i++)
ofStream.write((char*)&m_MapField[z][i], sizeof(m_MapField[z][i]));
//Close the file
ofStream.close();
return 0;
}
Mit LoadLvl wird eine neue Map gemacht und alles wieder geladen.
Code:
//Loads the level
void LoadLvl(std::string strFilename)
{
std::ifstream ifStream;
//Opens the file binary
ifStream.open(strFilename.c_str(),std::fstream::in | std::fstream::binary);
//Reads it
ifStream.read((char*)m_pLevelDatas,sizeof(*m_pLevelDatas));
this->MakeNewMap(m_pLevelDatas->iWidth, m_pLevelDatas->iHeight, "");
for(int z = 0;z < m_pLevelDatas->iWidth;z++)
for(int i = 0;i < m_pLevelDatas->iHeight;i++)
ifStream.read((char*)&m_MapField[z][i],sizeof(m_MapField[z][i]));
//Close the file
ifStream.close();
}
Die Resize Funktion gibts in ein paar Tagen, damit habe ich im Moment noch Probleme.
Hier ist nochmal der gesammte Code.
.h Datei:
Code:
//********************* Level.h ****************************//
// This class manages the 2D Levels of the Engine //
//**************************************************************************************//
//Include files
#include <iostream>
#include <fstream>
#include <vector>
//Saves the stuff
struct LevelData
{
unsigned int iMapID;
unsigned int iWidth;
unsigned int iHeight;
};
//Saves the informations if its a teleport field
struct TeleportField
{
int iMapID;
int iTargetWidth;
int iTargetHeight;
int iTeleporttype;
};
//Saves the field informations
struct MapField
{
char* strBitmapName;
TeleportField Teleport;
};
//This class manages the 2D Levels
class Level
{
public:
//Initialize the class
void Init(LevelData *pLevelData)
{
this->Init(pLevelData,"",0,0);
}
//Initialize the class
void Init(LevelData *pLevelData,std::string strStandardFilename,int iWidth, int iHeight)
{
m_pLevelDatas = pLevelData;
m_pLevelDatas->iWidth = iWidth;
m_pLevelDatas->iHeight = iHeight;
if(iWidth && iHeight)
this->MakeNewMap(iWidth,iHeight,strStandardFilename);
}
void SetSizeOfMap(int iWidth, int iHeight)
{
}
//Makes a new map (deletes all old datas)
void MakeNewMap(int iWidth, int iHeight,std::string strStandardFilename)
{
m_pLevelDatas->iWidth = iWidth;
m_pLevelDatas->iHeight = iHeight;
//Clears it if there is already a version
if(!m_bMap)
m_bMap = true;
else
{
//Resize it an sets it 0
for(int i = 0;i < m_pLevelDatas->iWidth;i++)
m_MapField[i].clear();
m_MapField.clear();
}
//Creats iWidth elements
m_MapField.resize(m_pLevelDatas->iWidth);
//Creates for each iWidth element iHeight elements
for(int i = 0;i < m_pLevelDatas->iWidth;i++)
{
m_MapField[i].resize(m_pLevelDatas->iHeight);
for(int z = 0;z < m_pLevelDatas->iHeight;z++)
{
//Sets it 0/pStandardFilename
m_MapField[i][z].strBitmapName = (char*)strStandardFilename;
m_MapField[i][z].Teleport.iMapID = 0;
}
}
}
//Returns the Field
MapField* GetField(int iX, int iY)
{
if ((iX > m_pLevelDatas->iWidth) || (iY > m_pLevelDatas->iHeight))
{
std::cout << "Error, field doesn't exist." << std::endl;
}
return &m_MapField[iX-1][iY-1];
}
//Gives the map out, only for test (DOS)
void GiveMapOut()
{
for(int i = 0;i < m_pLevelDatas->iHeight;i++)
{
for(int z = 0;z < m_pLevelDatas->iWidth;z++)
{
std::cout << m_MapField[z][i].pBitmapName << " ";
}
std::cout << std::endl;
}
}
//Saves the level
int SaveLvl(std::string strFilename)
{
if(!m_bMap)
return 1;
std::ofstream ofStream;
//Opens the file binary
ofStream.open(strFilename.c_str(),std::fstream::out | std::fstream::binary);
//Writes the LevelData
ofStream.write((char*)m_pLevelDatas, sizeof(*m_pLevelDatas));
for(int z = 0;z < m_pLevelDatas->iWidth;z++)
for(int i = 0;i < m_pLevelDatas->iHeight;i++)
ofStream.write((char*)&m_MapField[z][i], sizeof(m_MapField[z][i]));
//Close the file
ofStream.close();
return 0;
}
//Loads the level
void LoadLvl(std::string strFilename)
{
std::ifstream ifStream;
//Opens the file binary
ifStream.open(strFilename.c_str(),std::fstream::in | std::fstream::binary);
//Reads it
ifStream.read((char*)m_pLevelDatas,sizeof(*m_pLevelDatas));
this->MakeNewMap(m_pLevelDatas->iWidth, m_pLevelDatas->iHeight, "");
for(int z = 0;z < m_pLevelDatas->iWidth;z++)
for(int i = 0;i < m_pLevelDatas->iHeight;i++)
ifStream.read((char*)&m_MapField[z][i],sizeof(m_MapField[z][i]));
//Close the file
ifStream.close();
}
private:
//If the map exist
bool m_bMap;
//The map
std::vector < std::vector <MapField> > m_MapField;
//This struct saves the header of the file
LevelData *m_pLevelDatas;
};
.cpp Datei:
Code:
//******************************* FOR MORE SEE THE .h file ********************************//
#include "Level.h"
#include <cstdio>
LevelData LevelInfo;
Level Lvl;
using namespace std;
int main()
{
LevelInfo.iMapID = 36;
Lvl.Init(&LevelInfo,"Test.jpg",5,4);
Lvl.GetField(1,1)->pBitmapName = "sdf.png";
Lvl.GetField(3,4)->pBitmapName = "sdf.png";
Lvl.SetSizeOfMap(1,1);
Lvl.GiveMapOut();
Lvl.SaveLvl("test.drg");
Lvl.LoadLvl("test.drg");
cout << "ende" << endl;
cout.flush();
getchar();
return 0;
}
Damit wäre unsere Levelklasse fertig, jetzt musst du nur noch das Programm dazu programmieren.
Jetzt würde ich dir empfehlen eine ScrollX & eine ScrollY Koordinate zu machen, díe sich erhöht, wenn man scrollt.
Danach zeichnest du in einer Schleife alle Felder, die im Sichtbereich des Bildschirms sind.
Pseudocode:
//Geht alle Felder durch
for(int iHeight = 0;iHeight < LevelInfo.iHeight;iHeight++)
{
for(int iWidth = 0;iWidth < LevelInfo.iWidth;iWidth++)
{
//Zeichnet das Sprite (pBitmapName)
//Wenn das Sprite nicht ausserhalb des Bildschirms ist (60 ober und links vom Bildschirm, und über den unteren und rechten Teil des Bildschirms
if(((x+ScrollX> -60) && (x+ScrollX < MeineXAuslösung) && ((y+ScrollY > -60) && (y+ScrollY < MeineXAuflösung))))
MeineDrawFunktion(Lvl.GetField(x,y)->pBitmapName,x+ScrollX,y+ScrollY);
//Sprite ist 50 Breit
x += 50;
}
//Sprite ist 50 hoch
y += 50;
}
Jetzt könnnen wir alle Felder ausgeben, es fehlt nur noch das wir sie verändern können.
Dazu machst du irgendwie ein Menü, oder sowas in der Art, in dem man den Bitmapname aussuchen kann.
Das ist unserer Pinsel, wenn wir jetzt auf ein Feld klicken, sollte dieses Feld das Bitmap zeichnen.
Jetzt gehen wir bei einem Mausklick in einer Schleife alle Felder durch, und schauen ob die Maus auf dem Feld ist, wenn ja, dann wird der Bitmapname vom Feld mit Lvl.GetField(x,y)->pBitmapName = pBitmap; geändert.
Es fehlt nur noch eine Liste mit Monstern, die du so wie die Felder einzeln speicherst
So, ich hoffe die kleine Einführung in die Welt der Leveleditor hat euch gefallen.
Wenn du Fragen hast, kannst du Sie natürlich im Forum stellen.
Have fun,
Simon Hecht
Gibt es noch irgendwelche Fragen, oder wollen Sie über den Artikel diskutieren?
Editieren Versionen Linkpartnerschaft Top Printversion
Haben Sie einen Fehler gefunden? Dann klicken Sie doch auf Editieren, und beheben den Fehler, keine Angst, Sie können nichts zerstören, das Tutorial kann wiederhergestellt werden
Sprachenübersicht/Programmierung/C / C++/ C#/Spieleprogrammierung/Programmieren eines einfachen 2D Leveleditors