diff --git a/Tiled地图解析思路.md b/Tiled地图解析思路.md index 19ce9e2..16239fb 100644 --- a/Tiled地图解析思路.md +++ b/Tiled地图解析思路.md @@ -10,10 +10,12 @@ - 总共有“地图大小”个瓦片,放入容器vector中 - 每个瓦片包含数据:Sprite,Type(例如solid类型) - 可能引用多个图块集,因此可先*载入并保存每个图块集的数据*(载入函数),其它瓦片层(以及对象层)也能继续引用 - - 通过data数组中的gid查找所需信息(查找函数),填充瓦片vector + - 通过data数组中的gid查找所需信息(查找函数,能获取自定义属性),填充瓦片vector - 创建包含 TileLayerComponent 的游戏对象(持有瓦片vector) - 如果 type : objectgroup 载入对象层 - 对象数据在"objects"字段中,每个对象都创建一个游戏对象 - 关注的要点:gid查找源图片信息,layer数据中确认目标位置(TransformComponent)信息 - - 如果没有gid,则代表自己绘制的形状(可能是碰撞盒、触发器等,未来按需处理) \ No newline at end of file + - 如果没有gid,则代表自己绘制的形状(可能是碰撞盒、触发器等,未来按需处理) + +- 自定义属性的获取方法:图块集中 获取 "tiles" --> 获取 "properties" \ No newline at end of file diff --git a/src/engine/component/tilelayer_component.cpp b/src/engine/component/tilelayer_component.cpp index c73301a..3555054 100644 --- a/src/engine/component/tilelayer_component.cpp +++ b/src/engine/component/tilelayer_component.cpp @@ -3,6 +3,7 @@ #include "../core/context.h" #include "../render/renderer.h" #include "../render/camera.h" +#include "../physics/physics_engine.h" #include namespace engine::component { @@ -54,6 +55,13 @@ void TileLayerComponent::render(engine::core::Context& context) { } } +void TileLayerComponent::clean() +{ + if (physics_engine_){ + physics_engine_->unregisterCollisionLayer(this); + } +} + 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); diff --git a/src/engine/component/tilelayer_component.h b/src/engine/component/tilelayer_component.h index bd193ac..f83ca96 100644 --- a/src/engine/component/tilelayer_component.h +++ b/src/engine/component/tilelayer_component.h @@ -12,6 +12,10 @@ namespace engine::core { class Context; } +namespace engine::physics { +class PhysicsEngine; +} + namespace engine::component { /** * @brief 定义瓦片的类型,用于游戏逻辑(例如碰撞)。 @@ -47,6 +51,7 @@ private: glm::vec2 offset_ = {0.0f, 0.0f}; ///< @brief 瓦片层在世界中的偏移量 (瓦片层通常不需要缩放及旋转,因此不引入Transform组件) // offset_ 最好也保持默认的0,以免增加不必要的复杂性 bool is_hidden_ = false; ///< @brief 是否隐藏(不渲染) + engine::physics::PhysicsEngine* physics_engine_ = nullptr; ///< @brief 物理引擎的指针, clean()函数中可能需要反注册 public: TileLayerComponent() = default; @@ -92,13 +97,14 @@ public: void setOffset(const glm::vec2& offset) { offset_ = offset; } ///< @brief 设置瓦片层的偏移量 void setHidden(bool hidden) { is_hidden_ = hidden; } ///< @brief 设置是否隐藏(不渲染) - + void setPhysicsEngine(engine::physics::PhysicsEngine* physics_engine) {physics_engine_ = physics_engine; } protected: // 核心循环方法 void init() override; void update(float, engine::core::Context&) override {} void render(engine::core::Context& context) override; + void clean() override; }; } // namespace engine::component diff --git a/src/engine/physics/physics_engine.cpp b/src/engine/physics/physics_engine.cpp index 643902d..4e345be 100644 --- a/src/engine/physics/physics_engine.cpp +++ b/src/engine/physics/physics_engine.cpp @@ -3,6 +3,7 @@ #include "../component/physics_component.h" #include "../component/transform_component.h" #include "../component/collider_component.h" +#include "../component/tilelayer_component.h" #include "../object/game_object.h" #include #include @@ -22,6 +23,19 @@ void PhysicsEngine::unregisterComponent(engine::component::PhysicsComponent* com spdlog::trace("物理组件注销完成。"); } +void PhysicsEngine::registerCollisionLayer(engine::component::TileLayerComponent *layer) +{ + layer->setPhysicsEngine(this); // 设置物理引擎指针 + collision_tile_layers_.push_back(layer); + spdlog::trace("碰撞瓦片图层注册完成。"); +} + +void PhysicsEngine::unregisterCollisionLayer(engine::component::TileLayerComponent* layer) { + auto it = std::remove(collision_tile_layers_.begin(), collision_tile_layers_.end(), layer); + collision_tile_layers_.erase(it, collision_tile_layers_.end()); + spdlog::trace("碰撞瓦片图层注销完成。"); +} + void PhysicsEngine::update(float delta_time) { // 每帧开始时先清空碰撞对列表 collision_pairs_.clear(); @@ -42,14 +56,8 @@ void PhysicsEngine::update(float delta_time) { pc->velocity_ += (pc->getForce() / pc->getMass()) * delta_time; pc->clearForce(); // 清除当前帧的力 - // 更新位置:S += v * dt - auto* tc = pc->getTransform(); - if (tc) { - tc->translate(pc->velocity_ * delta_time); - } - - // 限制最大速度:v = min(v, max_speed) - pc->velocity_ = glm::clamp(pc->velocity_, -max_speed_, max_speed_); + // 处理瓦片层碰撞(速度和位置的更新移入此函数) + resolveTileCollisions(pc, delta_time); } // 处理对象间碰撞 checkObjectCollisions(); @@ -84,4 +92,97 @@ void PhysicsEngine::checkObjectCollisions() } } +void PhysicsEngine::resolveTileCollisions(engine::component::PhysicsComponent* pc, float delta_time) { + // 检查组件是否有效 + auto* obj = pc->getOwner(); + if (!obj) return; + auto* tc = obj->getComponent(); + auto* cc = obj->getComponent(); + if (!tc || !cc || !cc->isActive() || cc->isTrigger()) return; + auto world_aabb = cc->getWorldAABB(); // 使用最小包围盒进行碰撞检测(简化) + auto obj_pos = world_aabb.position; + auto obj_size = world_aabb.size; + if (world_aabb.size.x <= 0.0f || world_aabb.size.y <= 0.0f) return; + // -- 检查结束, 正式开始处理 -- + + auto tolerance = 1.0f; // 检查右边缘和下边缘时,需要减1像素,否则会检查到下一行/列的瓦片 + auto ds = pc->velocity_ * delta_time; // 计算物体在delta_time内的位移 + auto new_obj_pos = obj_pos + ds; // 计算物体在delta_time后的新位置 + + // 遍历所有注册的碰撞瓦片层 + for (auto* layer : collision_tile_layers_) { + if (!layer) continue; + auto tile_size = layer->getTileSize(); + // 轴分离碰撞检测:先检查X方向是否有碰撞 (y方向使用初始值obj_pos.y) + if (ds.x > 0.0f) { + // 检查右侧碰撞,需要分别测试右上和右下角 + auto right_top_x = new_obj_pos.x + obj_size.x; + auto tile_x = static_cast(floor(right_top_x / tile_size.x)); // 获取x方向瓦片坐标 + // y方向坐标有两个,右上和右下 + auto tile_y = static_cast(floor(obj_pos.y / tile_size.y)); + auto tile_type_top = layer->getTileTypeAt({tile_x, tile_y}); // 右上角瓦片类型 + auto tile_y_bottom = static_cast(floor((obj_pos.y + obj_size.y - tolerance) / tile_size.y)); + auto tile_type_bottom = layer->getTileTypeAt({tile_x, tile_y_bottom}); // 右下角瓦片类型 + + if (tile_type_top == engine::component::TileType::SOLID || tile_type_bottom == engine::component::TileType::SOLID) { + // 撞墙了!速度归零,x方向移动到贴着墙的位置 + new_obj_pos.x = tile_x * layer->getTileSize().x - obj_size.x; + pc->velocity_.x = 0.0f; + } + } + else if (ds.x < 0.0f) { + // 检查左侧碰撞,需要分别测试左上和左下角 + auto left_top_x = new_obj_pos.x; + auto tile_x = static_cast(floor(left_top_x / tile_size.x)); // 获取x方向瓦片坐标 + // y方向坐标有两个,左上和左下 + auto tile_y = static_cast(floor(obj_pos.y / tile_size.y)); + auto tile_type_top = layer->getTileTypeAt({tile_x, tile_y}); // 左上角瓦片类型 + auto tile_y_bottom = static_cast(floor((obj_pos.y + obj_size.y - tolerance) / tile_size.y)); + auto tile_type_bottom = layer->getTileTypeAt({tile_x, tile_y_bottom}); // 左下角瓦片类型 + + if (tile_type_top == engine::component::TileType::SOLID || tile_type_bottom == engine::component::TileType::SOLID) { + // 撞墙了!速度归零,x方向移动到贴着墙的位置 + new_obj_pos.x = (tile_x + 1) * layer->getTileSize().x; + pc->velocity_.x = 0.0f; + } + } + // 轴分离碰撞检测:再检查Y方向是否有碰撞 (x方向使用初始值obj_pos.x) + if (ds.y > 0.0f) { + // 检查底部碰撞,需要分别测试左下和右下角 + auto bottom_left_y = new_obj_pos.y + obj_size.y; + auto tile_y = static_cast(floor(bottom_left_y / tile_size.y)); + + auto tile_x = static_cast(floor(obj_pos.x / tile_size.x)); + auto tile_type_left = layer->getTileTypeAt({tile_x, tile_y}); // 左下角瓦片类型 + auto tile_x_right = static_cast(floor((obj_pos.x + obj_size.x - tolerance) / tile_size.x)); + auto tile_type_right = layer->getTileTypeAt({tile_x_right, tile_y}); // 右下角瓦片类型 + + if (tile_type_left == engine::component::TileType::SOLID || tile_type_right == engine::component::TileType::SOLID) { + // 到达地面!速度归零,y方向移动到贴着地面的位置 + new_obj_pos.y = tile_y * layer->getTileSize().y - obj_size.y; + pc->velocity_.y = 0.0f; + } + } + else if (ds.y < 0.0f) { + // 检查顶部碰撞,需要分别测试左上和右上角 + auto top_left_y = new_obj_pos.y; + auto tile_y = static_cast(floor(top_left_y / tile_size.y)); + + auto tile_x = static_cast(floor(obj_pos.x / tile_size.x)); + auto tile_type_left = layer->getTileTypeAt({tile_x, tile_y}); // 左上角瓦片类型 + auto tile_x_right = static_cast(floor((obj_pos.x + obj_size.x - tolerance) / tile_size.x)); + auto tile_type_right = layer->getTileTypeAt({tile_x_right, tile_y}); // 右上角瓦片类型 + + if (tile_type_left == engine::component::TileType::SOLID || tile_type_right == engine::component::TileType::SOLID) { + // 撞到天花板!速度归零,y方向移动到贴着天花板的位置 + new_obj_pos.y = (tile_y + 1) * layer->getTileSize().y; + pc->velocity_.y = 0.0f; + } + } + } + // 更新物体位置,并限制最大速度 + tc->setPosition(new_obj_pos); + pc->velocity_ = glm::clamp(pc->velocity_, -max_speed_, max_speed_); +} + } // namespace engine::physics \ No newline at end of file diff --git a/src/engine/physics/physics_engine.h b/src/engine/physics/physics_engine.h index 59ceb55..3c44f3b 100644 --- a/src/engine/physics/physics_engine.h +++ b/src/engine/physics/physics_engine.h @@ -5,6 +5,7 @@ namespace engine::component { class PhysicsComponent; + class TileLayerComponent; } namespace engine::object { @@ -19,6 +20,7 @@ namespace engine::physics { class PhysicsEngine { private: std::vector components_; ///< @brief 注册的物理组件容器,非拥有指针 + std::vector collision_tile_layers_; ///< @brief 注册的碰撞瓦片图层容器 glm::vec2 gravity_ = {0.0f, 980.0f}; ///< @brief 默认重力值 (像素/秒^2, 相当于100像素对应现实1m) float max_speed_ = 500.0f; ///< @brief 最大速度 (像素/秒) @@ -37,8 +39,14 @@ public: void registerComponent(engine::component::PhysicsComponent* component); ///< @brief 注册物理组件 void unregisterComponent(engine::component::PhysicsComponent* component); ///< @brief 注销物理组件 + // 如果瓦片层需要进行碰撞检测则注册。(不需要则不必注册) + void registerCollisionLayer(engine::component::TileLayerComponent* layer); ///< @brief 注册用于碰撞检测的 TileLayerComponent + void unregisterCollisionLayer(engine::component::TileLayerComponent* layer);///< @brief 注销用于碰撞检测的 TileLayerComponent + void update(float delta_time); ///< @brief 核心循环:更新所有注册的物理组件的状态 void checkObjectCollisions(); ///< @brief 检测并处理对象之间的碰撞,并记录需要游戏逻辑处理的碰撞对。 + /// @brief 检测并处理游戏对象和瓦片层之间的碰撞。 + void resolveTileCollisions(engine::component::PhysicsComponent* pc, float delta_time); // 设置器/获取器 void setGravity(const glm::vec2& gravity) { gravity_ = gravity; } ///< @brief 设置全局重力加速度 diff --git a/src/engine/scene/level_loader.cpp b/src/engine/scene/level_loader.cpp index 7e01ca5..a2ab7e3 100644 --- a/src/engine/scene/level_loader.cpp +++ b/src/engine/scene/level_loader.cpp @@ -193,6 +193,34 @@ void LevelLoader::loadObjectLayer(const nlohmann::json& layer_json, Scene& scene } } +engine::component::TileType LevelLoader::getTileType(const nlohmann::json &tile_json) +{ + if (tile_json.contains("properties")) { + auto& properties = tile_json["properties"]; + for (auto& property : properties) { + if (property.contains("name") && property["name"] == "solid") { + auto is_solid = property.value("value", false); + return is_solid ? engine::component::TileType::SOLID : engine::component::TileType::NORMAL; + } + // TODO: 可以在这里添加更多的自定义属性处理逻辑 + } + } + return engine::component::TileType::NORMAL; +} + +engine::component::TileType LevelLoader::getTileTypeById(const nlohmann::json &tileset_json, int local_id) +{ + if (tileset_json.contains("tiles")) { + auto& tiles = tileset_json["tiles"]; + for (auto& tile : tiles) { + if (tile.contains("id") && tile["id"] == local_id) { + return getTileType(tile); + } + } + } + return engine::component::TileType::NORMAL; +} + engine::component::TileInfo LevelLoader::getTileInfoByGid(int gid) { if (gid == 0) { @@ -229,7 +257,8 @@ engine::component::TileInfo LevelLoader::getTileInfoByGid(int gid) static_cast(tile_size_.y) }; engine::render::Sprite sprite{texture_id, texture_rect}; - return engine::component::TileInfo(sprite, engine::component::TileType::NORMAL); // 目前只完成渲染,以后再考虑瓦片类型 + auto tile_type = getTileTypeById(tileset, local_id); // 获取瓦片类型(只有瓦片id,还没找具体瓦片json) + return engine::component::TileInfo(sprite, tile_type); } else { // 这是多图片的情况 if (!tileset.contains("tiles")) { // 没有tiles字段的话不符合数据格式要求,直接返回空的瓦片信息 spdlog::error("Tileset 文件 '{}' 缺少 'tiles' 属性。", tileset_it->first); @@ -258,7 +287,8 @@ engine::component::TileInfo LevelLoader::getTileInfoByGid(int gid) static_cast(tile_json.value("height", image_height)) }; engine::render::Sprite sprite{texture_id, texture_rect}; - return engine::component::TileInfo(sprite, engine::component::TileType::NORMAL); // 目前只完成渲染,以后再考虑瓦片类型 + auto tile_type = getTileType(tile_json); // 获取瓦片类型(已经有具体瓦片json了) + return engine::component::TileInfo(sprite, tile_type); } } } diff --git a/src/engine/scene/level_loader.h b/src/engine/scene/level_loader.h index 911f1ac..956172a 100644 --- a/src/engine/scene/level_loader.h +++ b/src/engine/scene/level_loader.h @@ -3,9 +3,11 @@ #include #include #include +#include namespace engine::component { struct TileInfo; +enum class TileType; } namespace engine::scene { @@ -36,6 +38,21 @@ private: void loadTileLayer(const nlohmann::json& layer_json, Scene& scene); ///< @brief 加载瓦片图层 void loadObjectLayer(const nlohmann::json& layer_json, Scene& scene); ///< @brief 加载对象图层 + /** + * @brief 根据瓦片json对象获取瓦片类型 + * @param tile_json 瓦片json数据 + * @return 瓦片类型 + */ + engine::component::TileType getTileType(const nlohmann::json& tile_json); + + /** + * @brief 根据(单一图片)图块集中的id获取瓦片类型 + * @param tileset_json 图块集json数据 + * @param local_id 图块集中的id + * @return 瓦片类型 + */ + engine::component::TileType getTileTypeById(const nlohmann::json& tileset_json, int local_id); + /** * @brief 根据全局 ID 获取瓦片信息。 * @param gid 全局 ID。 diff --git a/src/game/scene/game_scene.cpp b/src/game/scene/game_scene.cpp index 7db413e..3cfd9cd 100644 --- a/src/game/scene/game_scene.cpp +++ b/src/game/scene/game_scene.cpp @@ -5,6 +5,7 @@ #include "../../engine/component/sprite_component.h" #include "../../engine/component/physics_component.h" #include "../../engine/component/collider_component.h" +#include "../../engine/component/tilelayer_component.h" #include "../../engine/physics/physics_engine.h" #include "../../engine/scene/level_loader.h" #include "../../engine/input/input_manager.h" @@ -25,6 +26,16 @@ void GameScene::init() { engine::scene::LevelLoader level_loader; level_loader.loadLevel("assets/maps/level1.tmj", *this); + // 注册"main"层到物理引擎 + auto* main_layer = findGameObjectByName("main"); + if (main_layer) { + auto* tile_layer = main_layer->getComponent(); + if (tile_layer) { + context_.getPhysicsEngine().registerCollisionLayer(tile_layer); + spdlog::info("注册\"main\"层到物理引擎"); + } + } + // 创建 test_object createTestObject(); @@ -93,15 +104,20 @@ void GameScene::TestObject() { if (!test_object_) return; auto& input_manager = context_.getInputManager(); - + auto* pc = test_object_->getComponent(); + if (!pc) return; if (input_manager.isActionDown("move_left")) { - test_object_->getComponent()->translate(glm::vec2(-1, 0)); + pc->velocity_.x = -100.0f; + } else { + pc->velocity_.x *= 0.9f; } if (input_manager.isActionDown("move_right")) { - test_object_->getComponent()->translate(glm::vec2(1, 0)); + pc->velocity_.x = 100.0f; + } else { + pc->velocity_.x *= 0.9f; } if (input_manager.isActionPressed("jump")) { - test_object_->getComponent()->setVelocity(glm::vec2(0, -400)); + pc->velocity_.y = -400.0f; } }