diff --git a/CMakeLists.txt b/CMakeLists.txt index ea6a26b..8934839 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(engine) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(ENGINE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/include/Map.h b/include/Map.h index 92b8179..244322e 100644 --- a/include/Map.h +++ b/include/Map.h @@ -1,36 +1,52 @@ #pragma once -#include -#include +#include +#include #include +#include + +#include +#include +#include +#include class GameInternal; class Map { public: - Map() = default; - ~Map() = default; - - /*! - * - * \brief - * This loads a map - * - * \param path The path to the map file - * \return Boolean for success - * - */ - [[deprecated("ID based text files are not supported anymore, use .txm maps instead")]] - static void loadMap(const char* path, int sizeX, int sizeY, GameInternal* game, const std::map>* textureDict /* backreference */); - [[deprecated]] - static void addTile(unsigned long id, int x, int y, GameInternal* game, const std::map>* textureDict); - /*! * \brief Loads a .tmx map + * \details Loads a `.tmx` file and extracts all relevant data. Any entities (including tiles) are only spawned once * \param path Path to the `.tmx` map file - * + * \sa Map::generateTiles() */ - static void loadMapTmx(const char* path); + Map(const char* path); + void generateTiles(); //!< Generates the map based on the loaded definition private: - static void addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, const char* texturePath); -}; + // struct required for initialisation + struct MapData { + const std::vector* tileSets; + const std::vector* mapLayers; + const tmx::Vector2u* mapSize; + const tmx::Vector2u* mapTileSize; + const std::vector* texturePaths; + }; + + struct TileSetData { + std::string texturePath{}; + tmx::Vector2i textureSize; + uint32_t tileCount{}; + tmx::Vector2u tileCount2D; + uint32_t firstGID{}; + }; + + tmx::Map map; + Map::MapData mapData; + std::vector> tileConstructors; + + void loadTileLayer(const tmx::TileLayer& layer); + static void addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, std::string texturePath, bool hasCollision); + + template + static std::optional getLayerProperty(const std::vector& properties, std::string propertyName) { return std::nullopt; }; +}; \ No newline at end of file diff --git a/src/GameInternal.cpp b/src/GameInternal.cpp index 7f1ac27..0ef8c69 100644 --- a/src/GameInternal.cpp +++ b/src/GameInternal.cpp @@ -91,8 +91,6 @@ void GameInternal::init(const char* title, int xpos, int ypos, int width, int he Mix_Volume(-1, MIX_MAX_VOLUME); Mix_AllocateChannels(16); - map = new Map(); - // loading sounds // assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav"); // assets->addSoundEffect("steps", "assets/sound/steps.wav"); diff --git a/src/Map.cpp b/src/Map.cpp index 7204be2..dab109b 100644 --- a/src/Map.cpp +++ b/src/Map.cpp @@ -2,11 +2,11 @@ #include #include +#include #include -#include -#include -#include -#include +#include +#include +#include #include #include @@ -17,155 +17,170 @@ #include #include #include +#include -#include "Constants.h" +#include "ColliderComponent.h" #include "GameInternal.h" #include "SpriteComponent.h" #include "TextureManager.h" #include "TileComponent.h" #include "VEGO.h" -#include "tmxlite/Types.hpp" -void Map::loadMap(const char* path, int sizeX, int sizeY, GameInternal* game, const std::map>* textureDict /* backreference */) -{ - std::string tileIDstr; - char singleChar = 0; - std::ifstream mapFile; - mapFile.open(path); - if (!mapFile.is_open()) { - SDL_SetError("Error loading map: Couldn't open map file!"); - std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl; - SDL_ClearError(); - } +template<> std::optional Map::getLayerProperty(const std::vector& properties, std::string propertyName) { + auto zIndexIterator = std::ranges::find_if(properties, [propertyName](const tmx::Property& property) { + return property.getName().compare(propertyName) == 0; + }); - int x = 0, y = 0; // needed outside for-loop for error handling - for (; !mapFile.eof(); mapFile.get(singleChar)) - { - if (singleChar == ',' || singleChar == '\n') { - if (tileIDstr.empty()) - continue; - Map::addTile(std::stoi(tileIDstr), x * TILE_SIZE, y * TILE_SIZE, game, textureDict); - tileIDstr.clear(); - x++; - if (singleChar == '\n') { - if (x != sizeX) { - SDL_SetError("Error loading map: specified x size doesn't match map file!"); - std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl; - SDL_ClearError(); - } - x = 0; - y++; - continue; - } - continue; - } - if (!std::isdigit(singleChar)) continue; - tileIDstr += singleChar; - } - if (y != sizeY) { - SDL_SetError("Error loading map: specified y size doesn't match map file!"); - std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl; - SDL_ClearError(); - } - - mapFile.close(); -} - -void Map::addTile(unsigned long id, int x, int y, GameInternal* game, const std::map>* textureDict) // tile entity -{ - auto& tile(game->manager.addEntity()); - tile.addComponent(x, y, TILE_SIZE, TILE_SIZE, id, textureDict); - - if(tile.getComponent().hasCollision()) tile.addComponent("tile"/*tile.getComponent().getName().data()*/); - tile.addGroup((size_t)Entity::GroupLabel::MAPTILES); -} - -void Map::loadMapTmx(const char* path) -{ - tmx::Map map; - if (!map.load(path)) { - // TODO: log to console + if (zIndexIterator != properties.end() && zIndexIterator->getType() == tmx::Property::Type::Boolean) { + return zIndexIterator->getBoolValue(); } - const std::vector& tileSets = map.getTilesets(); + return std::nullopt; +} - const std::vector& mapLayers = map.getLayers(); - const auto mapSize = map.getTileCount(); - const auto mapTileSize = map.getTileSize(); +template<> std::optional Map::getLayerProperty(const std::vector& properties, std::string propertyName) +{ + auto zIndexIterator = std::ranges::find_if(properties, [propertyName](const tmx::Property& property) { + return property.getName().compare(propertyName) == 0; + }); + + if (zIndexIterator != properties.end() && zIndexIterator->getType() == tmx::Property::Type::Int) { + return zIndexIterator->getIntValue(); + } + + return std::nullopt; +} + +Map::Map(const char* path) +{ + if (!this->map.load(path)) { + // TODO: log to console + // TODO: error handling + } std::vector texturePaths = {}; - for (auto tileSet : tileSets) { + for (const auto& tileSet : map.getTilesets()) { texturePaths.emplace_back(tileSet.getImagePath()); } - for (auto& layer : mapLayers) { + this->mapData = { + &map.getTilesets(), + &map.getLayers(), + &map.getTileCount(), + &map.getTileSize(), + &texturePaths + }; + + + for (auto& layer : *this->mapData.mapLayers) { if (layer->getType() == tmx::Layer::Type::Tile) { - auto& tileLayer = layer->getLayerAs(); - - int zIndex = 0; - - const std::vector& properties = layer->getProperties(); - auto zIndexIterator = std::find_if(properties.begin(), properties.end(), [](const tmx::Property& property) { - return property.getName() == "zIndex"; - }); - - if (zIndexIterator != properties.end() && std::is_nothrow_convertiblegetType()), int>::value) { - zIndex = zIndexIterator->getIntValue(); - } - - const auto& tiles = tileLayer.getTiles(); - - for (auto i = 0u; i < tileSets.size(); i++) { - auto tilesetTexture = VEGO_Game().textureManager->loadTexture(texturePaths.at(i).c_str()); - tmx::Vector2i textureSize; - SDL_QueryTexture(tilesetTexture, nullptr, nullptr, &(textureSize.x), &(textureSize.y)); - - const auto tileCountX = textureSize.x / mapTileSize.x; - const auto tileCountY = textureSize.y / mapTileSize.y; - - for (auto idx = 0ul; idx < mapSize.x * mapSize.y; idx++) { - - if (idx >= tiles.size() || tiles[idx].ID < tileSets.at(i).getFirstGID() - || tiles[idx].ID >= (tileSets.at(i).getFirstGID() + tileSets.at(i).getTileCount())) { - continue; - } - - const auto x = idx % mapSize.x; - const auto y = idx / mapSize.x; - - auto idIndex = (tiles[idx].ID - tileSets.at(i).getFirstGID()); - - int u = idIndex % tileCountX; - int v = idIndex / tileCountY; - u *= mapTileSize.x; //TODO we should be using the tile set size, as this may be different from the map's grid size - v *= mapTileSize.y; - - //normalise the UV - u /= textureSize.x; - v /= textureSize.y; - - //vert pos - const float tilePosX = static_cast(x) * mapTileSize.x; - const float tilePosY = (static_cast(y) * mapTileSize.y); - - Map::addTile(tilePosX, tilePosY, mapTileSize, u, v, zIndex, texturePaths.at(i).c_str()); - } - } - if (layer->getType() == tmx::Layer::Type::Object) { - // spawn objects - continue; - } + loadTileLayer(layer->getLayerAs()); + continue; + } + if (layer->getType() == tmx::Layer::Type::Object) { + // spawn objects + continue; } } } -void Map::addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, const char* texturePath) +void Map::loadTileLayer(const tmx::TileLayer& layer) +{ + const std::vector& properties = layer.getProperties(); + int zIndex = getLayerProperty(properties, "zIndex").value_or(0); + bool collision = getLayerProperty(properties, "collision").value_or(false); + + const auto& tiles = layer.getTiles(); + + // for each tile set + auto tileConstructorRange = std::views::iota(0) + | std::views::take(this->mapData.tileSets->size()) + // return the tile set metadata + | std::views::transform([&](uint16_t i) { + const char* texturePath = this->mapData.texturePaths->at(i).c_str(); + + tmx::Vector2i textureSize; + SDL_QueryTexture( + VEGO_Game().textureManager->loadTexture(texturePath), + nullptr, + nullptr, + &(textureSize.x), + &(textureSize.y) + ); + + tmx::Vector2u tileCount2D = { textureSize.x / this->mapData.mapTileSize->x, textureSize.y / this->mapData.mapTileSize->y }; + + uint32_t tileCount = this->mapData.tileSets->at(i).getTileCount(); + uint32_t firstGID = this->mapData.tileSets->at(i).getFirstGID(); + + return TileSetData { texturePath, textureSize, tileCount, tileCount2D, firstGID }; + }) + | std::views::transform([=, this](const TileSetData& data) { + // for each tile on the tile set + return std::views::iota(0) + | std::views::take(this->mapData.mapSize->x * this->mapData.mapSize->y) + // only take tiles that are on the ID range of the tile set + | std::views::filter([=](uint16_t idx) { + return + idx < tiles.size() + && tiles[idx].ID >= data.firstGID + && tiles[idx].ID < (data.firstGID + data.tileCount); + }) + // extract tile data + | std::views::transform([=, this](uint16_t idx) { + const auto x = idx % this->mapData.mapSize->x; + const auto y = idx / this->mapData.mapSize->x; + + const auto idIndex = (tiles[idx].ID - data.firstGID); + + uint32_t u = idIndex % data.tileCount2D.x; + uint32_t v = idIndex / data.tileCount2D.y; + u *= this->mapData.mapTileSize->x; // TODO: we should be using the tile set size, as this may be different from the map's grid size + v *= this->mapData.mapTileSize->y; + + // normalise the UV + u /= data.textureSize.x; + v /= data.textureSize.y; + + // vert pos + const float tilePosX = static_cast(x) * this->mapData.mapTileSize->x; + const float tilePosY = (static_cast(y) * this->mapData.mapTileSize->y); + + // return tile data as a function to spawn said tile + return std::function( + [tilePosX, tilePosY, capture0 = *this->mapData.mapTileSize, u, v, zIndex, capture1 = data.texturePath, collision] { + Map::addTile(tilePosX, tilePosY, capture0, u, v, zIndex, capture1, collision); + } + ); + }); + }) + // 2D view to 1D vector; might be better keep as view with scene management + | std::views::join + | std::ranges::to(); + + this->tileConstructors.insert(this->tileConstructors.end(), tileConstructorRange.begin(), tileConstructorRange.end()); +} + +void Map::addTile(float x, float y, const tmx::Vector2u& mapTileSize, int u, int v, int zIndex, std::string texturePath, bool hasCollision) { auto& tile(VEGO_Game().manager.addEntity()); tile.addComponent(x, y, mapTileSize.x, mapTileSize.y, 1); - tile.addComponent(texturePath, v, u, zIndex); // why does uv need to be reversed? + tile.addComponent(texturePath.c_str(), v, u, zIndex); // why does uv need to be reversed? + + if (hasCollision) { + // tag currently does not have a clear purposes, TODO: figure out appropriate tag name + tile.addComponent("hello I am a collider of a tile!"); + tile.addGroup((size_t)Entity::GroupLabel::MAPTILES); + } +} + +void Map::generateTiles() +{ + std::ranges::for_each(this->tileConstructors, [](auto& function) { + function(); + }); } \ No newline at end of file