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