Tilemap Loader



  • Moin Community 🙂

    Ich bin dabei ein Super Mario Clone zu programmieren, um ein wenig Erfahrung in der 2D Videospielprogrammierung zu sammeln. Um das Projekt umzusetzen benutze ich die SFML, in die ich mich parallel zum Projekt einarbeite.

    Ich benutze ein Tilemap-System um die Map aufzubauen und habe dafür meine eigene Klassen geschrieben. Ich programmiere nur als Hobby in meiner Freizeit und das auch noch nicht sehr lange, deswegen wäre ich für jeden Tipp, Vorschlag oder Kritik sehr dankbar 🙂 🙂

    ResourceManager.h
    --------------------
    Wird benutzt um Ressourcen wie Bilder, Sounds etc. abzuspeichern um sie nicht immer wieder neu laden zu müssen

    #pragma once
    
    #ifndef RESOURCE_MANAGER_H
    #define RESOURCE_MANAGER_H
    
    #include <string>
    #include <map>
    #include <memory>
    
    namespace kl
    {
    
    template<typename identifier, typename resourceType>
    class ResourceManager
    {
    public:
    	ResourceManager();
    	~ResourceManager();
    
    	bool		 insert(const std::string &filename, identifier id);
    	void		 remove(identifier id);
    	resourceType &getResource(const identifier id) const;
    
    private:
    	std::map<identifier, std::unique_ptr<resourceType>> mContainer;
    };
    
    #include "ResourceManager.inl" // Implementatioun Methoden
    
    };
    
    #endif // RESOURCE_MANAGER_H
    

    ResourceManager.inl
    --------------------

    template<typename identifier, typename resourceType>
    ResourceManager<identifier, resourceType>::ResourceManager()
    	: mContainer()
    {
    
    }
    
    template<typename identifier, typename resourceType>
    ResourceManager<identifier, resourceType>::~ResourceManager()
    {
    	mContainer.clear();
    }
    
    template<typename identifier, typename resourceType>
    bool ResourceManager<identifier, resourceType>::insert(const std::string &filename, identifier id)
    {
    	std::unique_ptr<resourceType> res(new resourceType());
    	if(res->loadFromFile(filename))
    	{
    		mContainer.insert(std::make_pair(id, std::move(res)));
    		return true;
    	}
    	return false;
    }
    
    template<typename identifier, typename resourceType>
    void ResourceManager<identifier, resourceType>::remove(identifier id)
    {
    	auto it = mContainer.find(id);
    	if(it != mContainer.end())
    		mContainer.erase(it);
    }
    
    template<typename identifier, typename resourceType>
    resourceType &ResourceManager<identifier, resourceType>::getResource(const identifier id) const
    {
    	auto it = mContainer.find(id);
    	if(it != mContainer.end())
    		return *it->second;
    }
    

    MapLoader.h
    --------------
    Die Klasse dient zum reinen auslesen von Tile Daten aus einer *.txt Datei
    Die Daten sind im Format Tileset - Tile gespeichert
    Bsp: 1-3 -> Tileset 1, Block 3
    Die Tilesets sind *.png Bilddateien in der jeder Block nebeneinander liegt

    #pragma once
    
    #ifndef MAP_LOADER_H
    #define MAP_LOADER_H
    
    #include <SFML\Graphics.hpp>
    #include <vector>
    #include <fstream>
    #include <memory>
    #include <string>
    
    namespace kl
    { 
    
    struct TileData
    {
    	int xCounter = 0;  // Bsp. xCounter = 5, tile px breed = 36 : xpos tile = 180px
    	int yCounter = 0;  // Bsp. yCounter = 2, tile heischt = 18 : ypos tile = 36px
    	int tileset  = 0;
    	int tile     = 0;
    };
    
    class MapLoader
    {
    public:
    	MapLoader();
    	~MapLoader();
    
    	bool                                   load(const std::string &filename);
    	std::vector<std::unique_ptr<TileData>> &getData();
    
    private:
    	std::ifstream					       mFile;
    	std::vector<std::unique_ptr<TileData>> mTileData;
    };
    
    }; // namespace kl
    
    #endif // MAP_LOADER_H
    

    MapLoader.cpp
    -----------------

    #include "MapLoader.h"
    
    #include <iostream>
    
    namespace kl
    {
    
    MapLoader::MapLoader()
    	: mFile(),
    	  mTileData()
    {
    }
    
    MapLoader::~MapLoader()
    {
    }
    
    bool MapLoader::load(const std::string &filename)
    {
    	mFile.open(filename);
    	if(mFile.is_open())
    	{
    		std::string line;
    		int rowX, rowY;
    
    		rowY = 1;
    
    		while(std::getline(mFile, line))
    		{
    			rowX = 1;
    
    			line.erase(std::remove(line.begin(), line.end(), ' '), line.end());  // Leerzeichen löschen
    			for(int i = 0; i < line.length(); i++)
    			{
    				if(line[i] != '0' && line[i] != '-')
    				{
    					std::unique_ptr<TileData> tmp(new TileData);
    
    					if(rowX == 1)
    						tmp->xCounter = rowX;
    					else
    						tmp->xCounter = (rowX / 3) + 1 ;
    					tmp->yCounter = rowY;
    					tmp->tileset  = line[i] - '0';
    					tmp->tile     = line[i + 2] - '0';  // vun tileset ob tile sprangen
    
    					mTileData.push_back(std::move(tmp));
    
    					// 1 TileData geliers, weider goen
    					i += 2;
    					rowX += 2;
    				} 
    				++rowX;
    			}
    			++rowY;
    		}
    	}
    	else {
    		std::cout << "Konnt Datei: " << filename << " net fannen/opmaan!" << std::endl;
    		return false;
    	}
    	mFile.close();
    	return true;
    }
    
    std::vector<std::unique_ptr<TileData>>& MapLoader::getData()
    {
    	return mTileData;
    }
    
    };
    

    MapManager.h
    --------------
    Erstellt die Map und zeichnet sie ins Fenster.
    Ist erstmal eine kleine Version die soweit funktioniert. Später soll diese noch erkennen was gezeichnet werden muss, und was nicht

    #pragma once
    
    #ifndef MAP_MANAGER_H
    #define MAP_MANAGER_H
    
    #include "MapLoader.h"
    #include "ResourceManager.h"
    
    namespace kl
    { 
    
    enum class Tileset
    {
    	kNone = 0,
    	kGround
    };
    
    enum class Level
    {
    	kTest = 0,
    	kLvl1
    };
    
    class MapManager
    {
    public:
    	MapManager();
    	~MapManager();
    
    	void init(Level lvlID);
    	void draw(sf::RenderWindow &wnd);
    
    private:
    	std::vector<std::unique_ptr<TileData>>   mMapData;
    	ResourceManager<Tileset, sf::Texture>    mTextures;
    	std::vector<std::unique_ptr<sf::Sprite>> mSprite;
    
    	void build();
    };
    
    }; // namespace kl
    
    #endif // MAP_MANAGER_H
    

    MapManager.cpp
    -----------------

    #include "MapManager.h"
    
    #include <iostream>
    
    namespace kl
    {
    
    MapManager::MapManager()
    	: mMapData(),
    	  mTextures(),
    	  mSprite()
    {
    
    }
    
    MapManager::~MapManager()
    {
    }
    
    void MapManager::init(Level lvlID)
    {
    	MapLoader map;
    	switch(lvlID)
    	{
    	case Level::kTest:
    		if(map.load("Map/test.txt"))
    		{
    			mMapData = std::move(map.getData());
    			mTextures.insert("Assets/IMG/testSet.png", Tileset::kGround);
    			mSprite.reserve(mMapData.size());
    			build();
    		}
    		break;
    
    	case Level::kLvl1:
    		break;
    
    	default:
    		break;
    	}
    }
    
    void MapManager::build()
    {
    	for(int i = 0; i < mMapData.size(); i++)
    	{
    		std::unique_ptr<sf::Sprite> tmp(new sf::Sprite());
    		sf::IntRect rec;
    
    		switch(mMapData[i]->tileset)
    		{
    		case (int)Tileset::kNone:
    			break;
    
    		case (int)Tileset::kGround:  // Ground tiles hun emmer 36px * 36px
    			rec.left = (mMapData[i]->tile - 1) * 36;
    			rec.top    = 0;
    			rec.width  = 36;
    			rec.height = 36;
    
    			tmp->setTexture(mTextures.getResource(Tileset::kGround));
    			tmp->setTextureRect(rec);
    			tmp->setPosition(mMapData[i]->xCounter * 36, mMapData[i]->yCounter * 36);
    
    			mSprite.push_back(std::move(tmp));
    			break;
    
    		default:
    			break;
    		}
    	}
    }
    
    void MapManager::draw(sf::RenderWindow &wnd)
    {
    	for(int i = 0; i < mSprite.size(); i++)
    		wnd.draw(*mSprite[i]);
    }
    
    };
    

    Danke 🙂
    MfG bommelmutz



  • Hi,

    was willst du denn jetzt? Kritik?

    Ob die Klassen jetzt schlau sind, so wie sie sind, kann ich nicht beurteilen.

    Am Code an sich fallen mir spontan die überall unnötigen (und daher irritierenden) Destruktoren auf und das switch in MapManager::build() ist schäbig.
    Was soll der int-cast?
    Du kannst in einem case auch einen neuen Scope aufmachen oder noch besser eine Funktion aufrufen, statt in jedem Fall Objekte zu erzeugen, die du ggf. nicht brauchst.
    pragma once und Include-guards sind auch unnötig. Mach ersteres weg.

    Das File-handling in MapLoader::load gefällt mir auch nicht. Wofür ist file ein member? Fehler nach cout, obwohl du bald vielleicht keine Konsole mehr hast.

    Man kann deinen Code aber ganz gut lesen; wolltest aber wohl ja hauptsächlich Kritik hören.



  • Hi 🙂 danke für deine Antwort. Ja ich wollte hauptsächlich hören was ich besser machen könnte

    dass das switch in MapManager::build() schäbig ist, ja da stimme ich dir zu. Der cast war auch nur für Visual Studio da er mir das rot markiert hat, und mich das gestört hat :p Hab das jetzt anders gelöst:

    void MapManager::build()  // Erstellt dei eenzel Tiles
    {
    	int tileWidth = 36;
    	for(int i = 0; i < mMapData.size(); i++)
    	{
    		std::unique_ptr<sf::Sprite> tmp = std::make_unique<sf::Sprite>();
    		sf::IntRect                 rec;
    
    		rec.left   = (mMapData[i]->tile - 1) * tileWidth;
    		rec.top    = 0;
    		rec.width  = tileWidth;
    		rec.height = tileWidth;
    
    		tmp->setTexture(mTextures.getResource(static_cast<Tileset>(mMapData[i]->tileset)));
    		tmp->setTextureRect(rec);
    		tmp->setPosition((mMapData[i]->xCounter - 1) * tileWidth, (mMapData[i]->yCounter - 1) * tileWidth);
    
    		mSprite.push_back(std::move(tmp));
    	}
    }
    

    Vielleicht ist es nicht die beste Idee aber ich erstelle bewusst gerne alle Tiles auf einmal, dadurch fällt das ständige erstellen/löschen weg. Die Methode MapManager::draw(sf::RenderWindow &wnd) soll später selbst erkennen was dargestellt werden muss, also nur die Tiles darstellen die im Fenster auch zu sehen sind, abhängig von der Position des Spielers.

    Wie würdest du das File-handling am besten umsetzen? 🙂

    Mfg bommelmutz


Anmelden zum Antworten