完成关卡加载器-瓦片层
This commit is contained in:
@@ -46,6 +46,7 @@ add_executable(${TARGET}
|
||||
src/engine/component/sprite_component.cpp
|
||||
src/engine/component/transform_component.cpp
|
||||
src/engine/component/parallax_component.cpp
|
||||
src/engine/component/tilelayer_component.cpp
|
||||
src/engine/scene/scene.cpp
|
||||
src/engine/scene/scene_manager.cpp
|
||||
src/engine/scene/level_loader.cpp
|
||||
|
||||
+10
-1
@@ -1,7 +1,16 @@
|
||||
# Tiled地图解析思路
|
||||
- 获取tilesets数组并载入图块集的数据
|
||||
|
||||
- 获取layers数组并遍历对象
|
||||
- 如果 type : imagelayer,载入单一图片
|
||||
- 关注"parallax","repeat","offset"字段
|
||||
- 创建包含 ParallaxComponent 的游戏对象
|
||||
- 如果 type : tilelayer ……
|
||||
|
||||
- 如果 type : tilelayer,载入瓦片层
|
||||
- 总共有“地图大小”个瓦片,放入容器vector中
|
||||
- 每个瓦片包含数据:Sprite,Type(例如solid类型)
|
||||
- 可能引用多个图块集,因此可先*载入并保存每个图块集的数据*(载入函数),其它瓦片层(以及对象层)也能继续引用
|
||||
- 通过data数组中的gid查找所需信息(查找函数),填充瓦片vector
|
||||
- 创建包含 TileLayerComponent 的游戏对象(持有瓦片vector)
|
||||
|
||||
- 如果 type : objectgroup ……
|
||||
@@ -0,0 +1,85 @@
|
||||
#include "tilelayer_component.h"
|
||||
#include "../object/game_object.h"
|
||||
#include "../core/context.h"
|
||||
#include "../render/renderer.h"
|
||||
#include "../render/camera.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace engine::component {
|
||||
|
||||
TileLayerComponent::TileLayerComponent(glm::ivec2 tile_size, glm::ivec2 map_size, std::vector<TileInfo>&& tiles)
|
||||
: tile_size_(tile_size),
|
||||
map_size_(map_size),
|
||||
tiles_(std::move(tiles))
|
||||
{
|
||||
if (tiles_.size() != static_cast<size_t>(map_size_.x * map_size_.y)) {
|
||||
spdlog::error("TileLayerComponent: 地图尺寸与提供的瓦片向量大小不匹配。瓦片数据将被清除。");
|
||||
tiles_.clear();
|
||||
map_size_ = {0, 0};
|
||||
}
|
||||
spdlog::trace("TileLayerComponent 构造完成");
|
||||
}
|
||||
|
||||
void TileLayerComponent::init() {
|
||||
if (!owner_) {
|
||||
spdlog::warn("TileLayerComponent 的 owner_ 未设置。");
|
||||
}
|
||||
spdlog::trace("TileLayerComponent 初始化完成");
|
||||
}
|
||||
|
||||
void TileLayerComponent::render(engine::core::Context& context) {
|
||||
if (tile_size_.x <= 0 || tile_size_.y <= 0) {
|
||||
return; // 防止除以零或无效尺寸
|
||||
}
|
||||
// 遍历所有瓦片
|
||||
for (int y = 0; y < map_size_.y; ++y) {
|
||||
for (int x = 0; x < map_size_.x; ++x) {
|
||||
size_t index = static_cast<size_t>(y) * map_size_.x + x;
|
||||
// 检查索引有效性以及瓦片是否需要渲染
|
||||
if (index < tiles_.size() && tiles_[index].type != TileType::EMPTY) {
|
||||
const auto& tile_info = tiles_[index];
|
||||
// 计算该瓦片在世界中的左上角位置 (drawSprite 预期接收左上角坐标)
|
||||
glm::vec2 tile_left_top_pos = {
|
||||
offset_.x + static_cast<float>(x) * tile_size_.x,
|
||||
offset_.y + static_cast<float>(y) * tile_size_.y
|
||||
};
|
||||
// 但如果图片的大小与瓦片的大小不一致,需要调整 y 坐标 (瓦片层的对齐点是左下角)
|
||||
if(static_cast<int>(tile_info.sprite.getSourceRect()->h) != tile_size_.y) {
|
||||
tile_left_top_pos.y -= (tile_info.sprite.getSourceRect()->h - static_cast<float>(tile_size_.y));
|
||||
}
|
||||
// 执行绘制
|
||||
context.getRenderer().drawSprite(context.getCamera(), tile_info.sprite, tile_left_top_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TileInfo* TileLayerComponent::getTileInfoAt(glm::ivec2 pos) const {
|
||||
if (pos.x < 0 || pos.x >= map_size_.x || pos.y < 0 || pos.y >= map_size_.y) {
|
||||
spdlog::warn("TileLayerComponent: 瓦片坐标越界: ({}, {})", pos.x, pos.y);
|
||||
return nullptr;
|
||||
}
|
||||
size_t index = static_cast<size_t>(pos.y * map_size_.x + pos.x);
|
||||
// 瓦片索引不能越界
|
||||
if (index < tiles_.size()) {
|
||||
return &tiles_[index];
|
||||
}
|
||||
spdlog::warn("TileLayerComponent: 瓦片索引越界: {}", index);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TileType TileLayerComponent::getTileTypeAt(glm::ivec2 pos) const {
|
||||
const TileInfo* info = getTileInfoAt(pos);
|
||||
return info ? info->type : TileType::EMPTY;
|
||||
}
|
||||
|
||||
TileType TileLayerComponent::getTileTypeAtWorldPos(const glm::vec2& world_pos) const {
|
||||
glm::vec2 relative_pos = world_pos - offset_;
|
||||
|
||||
int tile_x = static_cast<int>(std::floor(relative_pos.x / tile_size_.x));
|
||||
int tile_y = static_cast<int>(std::floor(relative_pos.y / tile_size_.y));
|
||||
|
||||
return getTileTypeAt(glm::ivec2{tile_x, tile_y});
|
||||
}
|
||||
|
||||
} // namespace engine::component
|
||||
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
#include "../render/sprite.h"
|
||||
#include "component.h"
|
||||
#include <vector>
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
namespace engine::render {
|
||||
class Sprite;
|
||||
}
|
||||
|
||||
namespace engine::core {
|
||||
class Context;
|
||||
}
|
||||
|
||||
namespace engine::component {
|
||||
/**
|
||||
* @brief 定义瓦片的类型,用于游戏逻辑(例如碰撞)。
|
||||
*/
|
||||
enum class TileType {
|
||||
EMPTY, ///< @brief 空白瓦片
|
||||
NORMAL, ///< @brief 普通瓦片
|
||||
SOLID, ///< @brief 静止可碰撞瓦片
|
||||
// 未来补充其它类型
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 包含单个瓦片的渲染和逻辑信息。
|
||||
*/
|
||||
struct TileInfo {
|
||||
render::Sprite sprite; ///< @brief 瓦片的视觉表示
|
||||
TileType type; ///< @brief 瓦片的逻辑类型
|
||||
TileInfo(render::Sprite s = render::Sprite(), TileType t = TileType::EMPTY) : sprite(std::move(s)), type(t) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 管理和渲染瓦片地图层。
|
||||
*
|
||||
* 存储瓦片地图的布局、每个瓦片的精灵信息和类型。
|
||||
* 负责在渲染阶段绘制可见的瓦片。
|
||||
*/
|
||||
class TileLayerComponent final : public Component {
|
||||
friend class engine::object::GameObject;
|
||||
private:
|
||||
glm::ivec2 tile_size_; ///< @brief 单个瓦片尺寸(像素)
|
||||
glm::ivec2 map_size_; ///< @brief 地图尺寸(瓦片数)
|
||||
std::vector<TileInfo> tiles_; ///< @brief 存储所有瓦片信息 (按"行主序"存储, index = y * map_width_ + x)
|
||||
glm::vec2 offset_ = {0.0f, 0.0f}; ///< @brief 瓦片层在世界中的偏移量 (瓦片层通常不需要缩放及旋转,因此不引入Transform组件)
|
||||
// offset_ 最好也保持默认的0,以免增加不必要的复杂性
|
||||
bool is_hidden_ = false; ///< @brief 是否隐藏(不渲染)
|
||||
|
||||
public:
|
||||
TileLayerComponent() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param tile_size 单个瓦片尺寸(像素)
|
||||
* @param map_size 地图尺寸(瓦片数)
|
||||
* @param tiles 初始化瓦片数据的容器 (会被移动)
|
||||
*/
|
||||
TileLayerComponent(glm::ivec2 tile_size, glm::ivec2 map_size, std::vector<TileInfo>&& tiles);
|
||||
|
||||
/**
|
||||
* @brief 根据瓦片坐标获取瓦片信息
|
||||
* @param pos 瓦片坐标 (0 <= x < map_size_.x, 0 <= y < map_size_.y)
|
||||
* @return const TileInfo* 指向瓦片信息的指针,如果坐标无效则返回 nullptr
|
||||
*/
|
||||
const TileInfo* getTileInfoAt(glm::ivec2 pos) const;
|
||||
|
||||
/**
|
||||
* @brief 根据瓦片坐标获取瓦片类型
|
||||
* @param pos 瓦片坐标 (0 <= x < map_size_.x, 0 <= y < map_size_.y)
|
||||
* @return TileType 瓦片类型,如果坐标无效则返回 TileType::EMPTY
|
||||
*/
|
||||
TileType getTileTypeAt(glm::ivec2 pos) const;
|
||||
|
||||
/**
|
||||
* @brief 根据世界坐标获取瓦片类型
|
||||
* @param world_pos 世界坐标
|
||||
* @return TileType 瓦片类型,如果坐标无效或对应空瓦片则返回 TileType::EMPTY
|
||||
*/
|
||||
TileType getTileTypeAtWorldPos(const glm::vec2& world_pos) const;
|
||||
|
||||
// getters and setters
|
||||
glm::ivec2 getTileSize() const { return tile_size_; } ///< @brief 获取单个瓦片尺寸
|
||||
glm::ivec2 getMapSize() const { return map_size_; } ///< @brief 获取地图尺寸
|
||||
glm::vec2 getWorldSize() const { ///< @brief 获取地图世界尺寸
|
||||
return glm::vec2(map_size_.x * tile_size_.x, map_size_.y * tile_size_.y);
|
||||
}
|
||||
const std::vector<TileInfo>& getTiles() const { return tiles_; } ///< @brief 获取瓦片容器
|
||||
const glm::vec2& getOffset() const { return offset_; } ///< @brief 获取瓦片层的偏移量
|
||||
bool isHidden() const { return is_hidden_; } ///< @brief 获取是否隐藏(不渲染)
|
||||
|
||||
void setOffset(const glm::vec2& offset) { offset_ = offset; } ///< @brief 设置瓦片层的偏移量
|
||||
void setHidden(bool hidden) { is_hidden_ = hidden; } ///< @brief 设置是否隐藏(不渲染)
|
||||
|
||||
|
||||
protected:
|
||||
// 核心循环方法
|
||||
void init() override;
|
||||
void update(float, engine::core::Context&) override {}
|
||||
void render(engine::core::Context& context) override;
|
||||
};
|
||||
|
||||
} // namespace engine::component
|
||||
@@ -19,6 +19,11 @@ private:
|
||||
bool is_flipped_ = false; ///< @brief 是否水平翻转
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数(创建一个空的/无效的精灵)
|
||||
*/
|
||||
Sprite() = default;
|
||||
|
||||
/**
|
||||
* @brief 构造一个精灵
|
||||
*
|
||||
|
||||
@@ -23,6 +23,11 @@ SDL_Texture* TextureManager::loadTexture(const std::string& file_path) {
|
||||
// 如果没加载则尝试加载纹理
|
||||
SDL_Texture* raw_texture = IMG_LoadTexture(renderer_, file_path.c_str());
|
||||
|
||||
// 载入纹理时,设置纹理缩放模式为最邻近插值(必不可少,否则TileLayer渲染中会出现边缘空隙/模糊)
|
||||
if (!SDL_SetTextureScaleMode(raw_texture, SDL_SCALEMODE_NEAREST)) {
|
||||
spdlog::warn("无法设置纹理缩放模式为最邻近插值");
|
||||
}
|
||||
|
||||
if (!raw_texture) {
|
||||
spdlog::error("加载纹理失败: '{}': {}", file_path, SDL_GetError());
|
||||
return nullptr;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include "level_loader.h"
|
||||
#include "../component/parallax_component.h"
|
||||
#include "../component/transform_component.h"
|
||||
#include "../component/tilelayer_component.h"
|
||||
#include "../object/game_object.h"
|
||||
#include "../scene/scene.h"
|
||||
#include "../core/context.h"
|
||||
#include "../render/sprite.h"
|
||||
#include "../utils/math.h"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <spdlog/spdlog.h>
|
||||
@@ -14,7 +16,6 @@
|
||||
namespace engine::scene {
|
||||
|
||||
bool LevelLoader::loadLevel(const std::string& level_path, Scene& scene) {
|
||||
map_path_ = level_path;
|
||||
// 1. 加载 JSON 文件
|
||||
std::ifstream file(level_path);
|
||||
if (!file.is_open()) {
|
||||
@@ -31,7 +32,26 @@ bool LevelLoader::loadLevel(const std::string& level_path, Scene& scene) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 加载图层数据
|
||||
// 3. 获取基本地图信息 (名称、地图尺寸、瓦片尺寸)
|
||||
map_path_ = level_path;
|
||||
map_size_ = glm::ivec2(json_data.value("width", 0), json_data.value("height", 0));
|
||||
tile_size_ = glm::ivec2(json_data.value("tilewidth", 0), json_data.value("tileheight", 0));
|
||||
|
||||
// 4. 加载 tileset 数据
|
||||
if (json_data.contains("tilesets") && json_data["tilesets"].is_array()) {
|
||||
for (const auto& tileset_json : json_data["tilesets"]) {
|
||||
if (!tileset_json.contains("source") || !tileset_json["source"].is_string() ||
|
||||
!tileset_json.contains("firstgid") || !tileset_json["firstgid"].is_number_integer()) {
|
||||
spdlog::error("tilesets 对象中缺少有效 'source' 或 'firstgid' 字段。");
|
||||
continue;
|
||||
}
|
||||
auto tileset_path = resolvePath(tileset_json["source"], map_path_); // 支持隐式转换,可以省略.get<T>()方法,
|
||||
auto first_gid = tileset_json["firstgid"];
|
||||
loadTileset(tileset_path, first_gid);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 加载图层数据
|
||||
if (!json_data.contains("layers") || !json_data["layers"].is_array()) { // 地图文件中必须有 layers 数组
|
||||
spdlog::error("地图文件 '{}' 中缺少或无效的 'layers' 数组。", level_path);
|
||||
return false;
|
||||
@@ -67,7 +87,7 @@ void LevelLoader::loadImageLayer(const nlohmann::json& layer_json, Scene& scene)
|
||||
spdlog::error("图层 '{}' 缺少 'image' 属性。", layer_json.value("name", "Unnamed"));
|
||||
return;
|
||||
}
|
||||
auto texture_id = resolvePath(image_path);
|
||||
auto texture_id = resolvePath(image_path, map_path_);
|
||||
|
||||
// 获取图层偏移量(json中没有则代表未设置,给默认值即可)
|
||||
const glm::vec2 offset = glm::vec2(layer_json.value("offsetx", 0.0f), layer_json.value("offsety", 0.0f));
|
||||
@@ -91,9 +111,32 @@ void LevelLoader::loadImageLayer(const nlohmann::json& layer_json, Scene& scene)
|
||||
spdlog::info("加载图层: '{}' 完成", layer_name);
|
||||
}
|
||||
|
||||
void LevelLoader::loadTileLayer(const nlohmann::json&, Scene&)
|
||||
void LevelLoader::loadTileLayer(const nlohmann::json& layer_json, Scene& scene)
|
||||
{
|
||||
// TODO
|
||||
if (!layer_json.contains("data") || !layer_json["data"].is_array()) {
|
||||
spdlog::error("图层 '{}' 缺少 'data' 属性。", layer_json.value("name", "Unnamed"));
|
||||
return;
|
||||
}
|
||||
// 准备 TileInfo Vector (瓦片数量 = 地图宽度 * 地图高度)
|
||||
std::vector<engine::component::TileInfo> tiles;
|
||||
tiles.reserve(map_size_.x * map_size_.y);
|
||||
|
||||
// 获取图层数据 (瓦片 ID 列表)
|
||||
const auto& data = layer_json["data"];
|
||||
|
||||
// 根据gid获取必要信息,并依次填充 TileInfo Vector
|
||||
for (const auto& gid : data) {
|
||||
tiles.push_back(getTileInfoByGid(gid));
|
||||
}
|
||||
|
||||
// 获取图层名称
|
||||
const std::string& layer_name = layer_json.value("name", "Unnamed");
|
||||
// 创建游戏对象
|
||||
auto game_object = std::make_unique<engine::object::GameObject>(layer_name);
|
||||
// 添加Tilelayer组件
|
||||
game_object->addComponent<engine::component::TileLayerComponent>(tile_size_, map_size_, std::move(tiles));
|
||||
// 添加到场景中
|
||||
scene.addGameObject(std::move(game_object));
|
||||
}
|
||||
|
||||
void LevelLoader::loadObjectLayer(const nlohmann::json&, Scene&)
|
||||
@@ -101,18 +144,112 @@ void LevelLoader::loadObjectLayer(const nlohmann::json&, Scene&)
|
||||
// TODO
|
||||
}
|
||||
|
||||
std::string LevelLoader::resolvePath(std::string image_path)
|
||||
engine::component::TileInfo LevelLoader::getTileInfoByGid(int gid)
|
||||
{
|
||||
if (gid == 0) {
|
||||
return engine::component::TileInfo();
|
||||
}
|
||||
|
||||
// upper_bound:查找tileset_data_中键大于 gid 的第一个元素,返回迭代器
|
||||
auto tileset_it = tileset_data_.upper_bound(gid);
|
||||
if (tileset_it == tileset_data_.begin()) {
|
||||
spdlog::error("gid为 {} 的瓦片未找到图块集。", gid);
|
||||
return engine::component::TileInfo();
|
||||
}
|
||||
--tileset_it; // 前移一个位置,这样就得到不大于gid的最近一个元素(我们需要的)
|
||||
|
||||
const auto& tileset = tileset_it->second;
|
||||
auto local_id = gid - tileset_it->first; // 计算瓦片在图块集中的局部ID
|
||||
const std::string file_path = tileset.value("file_path", ""); // 获取图块集文件路径
|
||||
if (file_path.empty()) {
|
||||
spdlog::error("Tileset 文件 '{}' 缺少 'file_path' 属性。", tileset_it->first);
|
||||
return engine::component::TileInfo();
|
||||
}
|
||||
// 图块集分为两种情况,需要分别考虑
|
||||
if (tileset.contains("image")) { // 这是单一图片的情况
|
||||
// 获取图片路径
|
||||
auto texture_id = resolvePath(tileset["image"].get<std::string>(), file_path);
|
||||
// 计算瓦片在图片网格中的坐标
|
||||
auto coordinate_x = local_id % tileset["columns"].get<int>();
|
||||
auto coordinate_y = local_id / tileset["columns"].get<int>();
|
||||
// 根据坐标确定源矩形
|
||||
SDL_FRect texture_rect = {
|
||||
static_cast<float>(coordinate_x * tile_size_.x),
|
||||
static_cast<float>(coordinate_y * tile_size_.y),
|
||||
static_cast<float>(tile_size_.x),
|
||||
static_cast<float>(tile_size_.y)
|
||||
};
|
||||
engine::render::Sprite sprite{texture_id, texture_rect};
|
||||
return engine::component::TileInfo(sprite, engine::component::TileType::NORMAL); // 目前只完成渲染,以后再考虑瓦片类型
|
||||
} else { // 这是多图片的情况
|
||||
if (!tileset.contains("tiles")) { // 没有tiles字段的话不符合数据格式要求,直接返回空的瓦片信息
|
||||
spdlog::error("Tileset 文件 '{}' 缺少 'tiles' 属性。", tileset_it->first);
|
||||
return engine::component::TileInfo();
|
||||
}
|
||||
// 遍历tiles数组,根据id查找对应的瓦片
|
||||
const auto& tiles_json = tileset["tiles"];
|
||||
for (const auto& tile_json : tiles_json) {
|
||||
auto tile_id = tile_json.value("id", 0);
|
||||
if (tile_id == local_id) { // 找到对应的瓦片,进行后续操作
|
||||
if (!tile_json.contains("image")) { // 没有image字段的话不符合数据格式要求,直接返回空的瓦片信息
|
||||
spdlog::error("Tileset 文件 '{}' 中瓦片 {} 缺少 'image' 属性。", tileset_it->first, tile_id);
|
||||
return engine::component::TileInfo();
|
||||
}
|
||||
// --- 接下来根据必要信息创建并返回 TileInfo ---
|
||||
// 获取图片路径
|
||||
auto texture_id = resolvePath(tile_json["image"].get<std::string>(), file_path);
|
||||
// 先确认图片尺寸
|
||||
auto image_width = tile_json.value("imagewidth", 0);
|
||||
auto image_height = tile_json.value("imageheight", 0);
|
||||
// 从json中获取源矩形信息
|
||||
SDL_FRect texture_rect = { // tiled中源矩形信息只有设置了才会有值,没有就是默认值
|
||||
static_cast<float>(tile_json.value("x", 0)),
|
||||
static_cast<float>(tile_json.value("y", 0)),
|
||||
static_cast<float>(tile_json.value("width", image_width)), // 如果未设置,则使用图片尺寸
|
||||
static_cast<float>(tile_json.value("height", image_height))
|
||||
};
|
||||
engine::render::Sprite sprite{texture_id, texture_rect};
|
||||
return engine::component::TileInfo(sprite, engine::component::TileType::NORMAL); // 目前只完成渲染,以后再考虑瓦片类型
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果能走到这里,说明查找失败,返回空的瓦片信息
|
||||
spdlog::error("图块集 '{}' 中未找到gid为 {} 的瓦片。", tileset_it->first, gid);
|
||||
return engine::component::TileInfo();
|
||||
}
|
||||
|
||||
void LevelLoader::loadTileset(const std::string& tileset_path, int first_gid)
|
||||
{
|
||||
std::ifstream tileset_file(tileset_path);
|
||||
if (!tileset_file.is_open()) {
|
||||
spdlog::error("无法打开 Tileset 文件: {}", tileset_path);
|
||||
return;
|
||||
}
|
||||
|
||||
nlohmann::json ts_json;
|
||||
try {
|
||||
tileset_file >> ts_json;
|
||||
} catch (const nlohmann::json::parse_error& e) {
|
||||
spdlog::error("解析 Tileset JSON 文件 '{}' 失败: {} (at byte {})", tileset_path, e.what(), e.byte);
|
||||
return;
|
||||
}
|
||||
ts_json["file_path"] = tileset_path; // 将文件路径存储到json中,后续解析图片路径时需要
|
||||
tileset_data_[first_gid] = std::move(ts_json);
|
||||
spdlog::info("Tileset 文件 '{}' 加载完成,firstgid: {}", tileset_path, first_gid);
|
||||
}
|
||||
|
||||
std::string LevelLoader::resolvePath(const std::string& relative_path, const std::string& file_path)
|
||||
{
|
||||
try {
|
||||
// 获取地图文件的父目录(相对于可执行文件) “assets/maps/level1.tmj” -> “assets/maps”
|
||||
auto map_dir = std::filesystem::path(map_path_).parent_path();
|
||||
// 获取地图文件的父目录(相对于可执行文件) "assets/maps/level1.tmj" -> "assets/maps"
|
||||
auto map_dir = std::filesystem::path(file_path).parent_path();
|
||||
// 合并路径(相对于可执行文件)并返回。 /* std::filesystem::canonical:解析路径中的当前目录(.)和上级目录(..)导航符,
|
||||
/* 得到一个干净的路径 */
|
||||
auto final_path = std::filesystem::canonical(map_dir / image_path);
|
||||
auto final_path = std::filesystem::canonical(map_dir / relative_path);
|
||||
return final_path.string();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("解析路径失败: {}", e.what());
|
||||
return image_path;
|
||||
return relative_path;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include <glm/vec2.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <map>
|
||||
|
||||
namespace engine::component {
|
||||
struct TileInfo;
|
||||
}
|
||||
|
||||
namespace engine::scene {
|
||||
class Scene;
|
||||
|
||||
/**
|
||||
* @brief 负责从 Tiled JSON 文件 (.tmj) 加载关卡数据到 Scene 中。
|
||||
*/
|
||||
class LevelLoader final {
|
||||
std::string map_path_; ///< @brief 地图路径(拼接路径时需要)
|
||||
glm::ivec2 map_size_; ///< @brief 地图尺寸(瓦片数量)
|
||||
glm::ivec2 tile_size_; ///< @brief 瓦片尺寸(像素)
|
||||
std::map<int, nlohmann::json> tileset_data_; ///< @brief firstgid -> 瓦片集数据
|
||||
|
||||
public:
|
||||
LevelLoader() = default;
|
||||
|
||||
@@ -23,15 +36,30 @@ private:
|
||||
void loadTileLayer(const nlohmann::json& layer_json, Scene& scene); ///< @brief 加载瓦片图层
|
||||
void loadObjectLayer(const nlohmann::json& layer_json, Scene& scene); ///< @brief 加载对象图层
|
||||
|
||||
/**
|
||||
* @brief 根据全局 ID 获取瓦片信息。
|
||||
* @param gid 全局 ID。
|
||||
* @return engine::component::TileInfo 瓦片信息。
|
||||
*/
|
||||
engine::component::TileInfo getTileInfoByGid(int gid);
|
||||
|
||||
/**
|
||||
* @brief 加载 Tiled tileset 文件 (.tsj)。
|
||||
* @param tileset_path Tileset 文件路径。
|
||||
* @param first_gid 此 tileset 的第一个全局 ID。
|
||||
*/
|
||||
void loadTileset(const std::string& tileset_path, int first_gid);
|
||||
|
||||
/**
|
||||
* @brief 解析图片路径,合并地图路径和相对路径。例如:
|
||||
* 1. 地图路径:"assets/maps/level1.tmj"
|
||||
* 1. 文件路径:"assets/maps/level1.tmj"
|
||||
* 2. 相对路径:"../textures/Layers/back.png"
|
||||
* 3. 最终路径:"assets/textures/Layers/back.png"
|
||||
* @param image_path (图片)相对路径
|
||||
* @param relative_path 相对路径(相对于文件)
|
||||
* @param file_path 文件路径
|
||||
* @return std::string 解析后的完整路径。
|
||||
*/
|
||||
std::string resolvePath(std::string image_path);
|
||||
std::string resolvePath(const std::string& relative_path, const std::string& file_path);
|
||||
};
|
||||
|
||||
} // namespace engine::scene
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
int main(int /* argc */, char* /* argv */[]) {
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
// spdlog::set_level(spdlog::level::debug);
|
||||
|
||||
engine::core::GameApp app;
|
||||
app.run();
|
||||
|
||||
Reference in New Issue
Block a user