mirror of
https://github.com/Nimac0/SDL_Minigame
synced 2026-01-12 07:53:43 +00:00
Merge branch 'tmxlite' into dev
This commit is contained in:
commit
a8052b4bbb
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15)
|
|||||||
|
|
||||||
project(engine)
|
project(engine)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
set(ENGINE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
set(ENGINE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|||||||
@ -1,36 +1,52 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <tmxlite/Types.hpp>
|
#include <functional>
|
||||||
#include <map>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <tmxlite/Map.hpp>
|
||||||
|
#include <tmxlite/Property.hpp>
|
||||||
|
#include <tmxlite/TileLayer.hpp>
|
||||||
|
#include <tmxlite/Types.hpp>
|
||||||
|
|
||||||
class GameInternal;
|
class GameInternal;
|
||||||
class Map
|
class Map
|
||||||
{
|
{
|
||||||
public:
|
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<int, std::pair<std::string, bool>>* textureDict /* backreference */);
|
|
||||||
[[deprecated]]
|
|
||||||
static void addTile(unsigned long id, int x, int y, GameInternal* game, const std::map<int, std::pair<std::string, bool>>* textureDict);
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Loads a .tmx map
|
* \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
|
* \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:
|
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<tmx::Tileset>* tileSets;
|
||||||
|
const std::vector<tmx::Layer::Ptr>* mapLayers;
|
||||||
|
const tmx::Vector2u* mapSize;
|
||||||
|
const tmx::Vector2u* mapTileSize;
|
||||||
|
const std::vector<std::string>* 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<std::function<void()>> 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<typename T>
|
||||||
|
static std::optional<T> getLayerProperty(const std::vector<tmx::Property>& properties, std::string propertyName) { return std::nullopt; };
|
||||||
|
};
|
||||||
@ -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_Volume(-1, MIX_MAX_VOLUME);
|
||||||
Mix_AllocateChannels(16);
|
Mix_AllocateChannels(16);
|
||||||
|
|
||||||
map = new Map();
|
|
||||||
|
|
||||||
// loading sounds
|
// loading sounds
|
||||||
// assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav");
|
// assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav");
|
||||||
// assets->addSoundEffect("steps", "assets/sound/steps.wav");
|
// assets->addSoundEffect("steps", "assets/sound/steps.wav");
|
||||||
|
|||||||
269
src/Map.cpp
269
src/Map.cpp
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <iostream>
|
#include <functional>
|
||||||
#include <fstream>
|
#include <optional>
|
||||||
#include <type_traits>
|
#include <ranges>
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <SDL_error.h>
|
#include <SDL_error.h>
|
||||||
@ -17,155 +17,170 @@
|
|||||||
#include <tmxlite/Tileset.hpp>
|
#include <tmxlite/Tileset.hpp>
|
||||||
#include <tmxlite/Property.hpp>
|
#include <tmxlite/Property.hpp>
|
||||||
#include <tmxlite/TileLayer.hpp>
|
#include <tmxlite/TileLayer.hpp>
|
||||||
|
#include <tmxlite/Types.hpp>
|
||||||
|
|
||||||
#include "Constants.h"
|
#include "ColliderComponent.h"
|
||||||
#include "GameInternal.h"
|
#include "GameInternal.h"
|
||||||
#include "SpriteComponent.h"
|
#include "SpriteComponent.h"
|
||||||
#include "TextureManager.h"
|
#include "TextureManager.h"
|
||||||
#include "TileComponent.h"
|
#include "TileComponent.h"
|
||||||
#include "VEGO.h"
|
#include "VEGO.h"
|
||||||
#include "tmxlite/Types.hpp"
|
|
||||||
|
|
||||||
void Map::loadMap(const char* path, int sizeX, int sizeY, GameInternal* game, const std::map<int, std::pair<std::string, bool>>* textureDict /* backreference */)
|
|
||||||
{
|
|
||||||
std::string tileIDstr;
|
|
||||||
char singleChar = 0;
|
|
||||||
std::ifstream mapFile;
|
|
||||||
mapFile.open(path);
|
|
||||||
|
|
||||||
if (!mapFile.is_open()) {
|
template<> std::optional<bool> Map::getLayerProperty(const std::vector<tmx::Property>& properties, std::string propertyName) {
|
||||||
SDL_SetError("Error loading map: Couldn't open map file!");
|
auto zIndexIterator = std::ranges::find_if(properties, [propertyName](const tmx::Property& property) {
|
||||||
std::cout << "ERROR: Map couldnt be loaded! " << SDL_GetError() << std::endl;
|
return property.getName().compare(propertyName) == 0;
|
||||||
SDL_ClearError();
|
});
|
||||||
}
|
|
||||||
|
|
||||||
int x = 0, y = 0; // needed outside for-loop for error handling
|
if (zIndexIterator != properties.end() && zIndexIterator->getType() == tmx::Property::Type::Boolean) {
|
||||||
for (; !mapFile.eof(); mapFile.get(singleChar))
|
return zIndexIterator->getBoolValue();
|
||||||
{
|
|
||||||
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<int, std::pair<std::string, bool>>* textureDict) // tile entity
|
|
||||||
{
|
|
||||||
auto& tile(game->manager.addEntity());
|
|
||||||
tile.addComponent<TileComponent>(x, y, TILE_SIZE, TILE_SIZE, id, textureDict);
|
|
||||||
|
|
||||||
if(tile.getComponent<TileComponent>().hasCollision()) tile.addComponent<ColliderComponent>("tile"/*tile.getComponent<TileComponent>().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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<tmx::Tileset>& tileSets = map.getTilesets();
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<tmx::Layer::Ptr>& mapLayers = map.getLayers();
|
template<> std::optional<int> Map::getLayerProperty(const std::vector<tmx::Property>& properties, std::string propertyName)
|
||||||
const auto mapSize = map.getTileCount();
|
{
|
||||||
const auto mapTileSize = map.getTileSize();
|
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<std::string> texturePaths = {};
|
std::vector<std::string> texturePaths = {};
|
||||||
|
|
||||||
for (auto tileSet : tileSets) {
|
for (const auto& tileSet : map.getTilesets()) {
|
||||||
texturePaths.emplace_back(tileSet.getImagePath());
|
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) {
|
if (layer->getType() == tmx::Layer::Type::Tile) {
|
||||||
auto& tileLayer = layer->getLayerAs<tmx::TileLayer>();
|
loadTileLayer(layer->getLayerAs<tmx::TileLayer>());
|
||||||
|
continue;
|
||||||
int zIndex = 0;
|
}
|
||||||
|
if (layer->getType() == tmx::Layer::Type::Object) {
|
||||||
const std::vector<tmx::Property>& properties = layer->getProperties();
|
// spawn objects
|
||||||
auto zIndexIterator = std::find_if(properties.begin(), properties.end(), [](const tmx::Property& property) {
|
continue;
|
||||||
return property.getName() == "zIndex";
|
|
||||||
});
|
|
||||||
|
|
||||||
if (zIndexIterator != properties.end() && std::is_nothrow_convertible<decltype(zIndexIterator->getType()), 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<float>(x) * mapTileSize.x;
|
|
||||||
const float tilePosY = (static_cast<float>(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<tmx::Property>& properties = layer.getProperties();
|
||||||
|
int zIndex = getLayerProperty<int>(properties, "zIndex").value_or(0);
|
||||||
|
bool collision = getLayerProperty<bool>(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<float>(x) * this->mapData.mapTileSize->x;
|
||||||
|
const float tilePosY = (static_cast<float>(y) * this->mapData.mapTileSize->y);
|
||||||
|
|
||||||
|
// return tile data as a function to spawn said tile
|
||||||
|
return std::function<void()>(
|
||||||
|
[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<std::vector>();
|
||||||
|
|
||||||
|
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());
|
auto& tile(VEGO_Game().manager.addEntity());
|
||||||
|
|
||||||
tile.addComponent<TransformComponent>(x, y, mapTileSize.x, mapTileSize.y, 1);
|
tile.addComponent<TransformComponent>(x, y, mapTileSize.x, mapTileSize.y, 1);
|
||||||
tile.addComponent<SpriteComponent>(texturePath, v, u, zIndex); // why does uv need to be reversed?
|
tile.addComponent<SpriteComponent>(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<ColliderComponent>("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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user