Tutorials - Programmieren eines einfachen 2D Leveleditors

Sprachenübersicht/Programmierung/C / C++/ C#/Spieleprogrammierung

Programmieren eines einfachen 2D Leveleditors

Diese Seite wurde 20046 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



Vorwort Top



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.

Feldstruktur Top



Zuerst 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 übergeben, damit auf die Struktur intern und extern zugegriffen werden kann (und auch von anderen Klassen)

Danach schreiben wir die Höhe * Breite Felder in die Datei. Unsere Struktur sieht dann so aus:

Feld Top



Code:


struct MapField
{
    char* strBitmapName;
    TeleportField Teleport;
};



Levelübergänge Top



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;
};



Grundgerüst Top



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 anders macht gibt es Probleme.

Auf den Vektor kann man dan problemlos per m_MapField[x][y] zugreifen, und er kann auch automatisch per Destruktor gelöscht werden.

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 dann 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.

Speichern/Laden Top



Jetzt brauchen wir nur noch eine Funktion 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.

Der gesammte Code Top



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.

Weitere Theorie Top



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 laugh

Schlusswort Top



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