From 5e4f6c587f84a86e7b0800d654abe2e24f857281 Mon Sep 17 00:00:00 2001 From: Ziyu Date: Sun, 8 Jun 2025 12:01:33 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=8E=A9=E5=AE=B6=E4=B8=8E?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E5=AF=B9=E8=B1=A1=E4=BA=A4=E4=BA=92-2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/maps/prop.tsj | 16 +++++- src/engine/component/tilelayer_component.h | 1 + src/engine/physics/physics_engine.cpp | 58 ++++++++++++++++++++-- src/engine/physics/physics_engine.h | 11 ++++ src/engine/scene/level_loader.cpp | 8 +++ src/game/scene/game_scene.cpp | 26 ++++++++++ src/game/scene/game_scene.h | 1 + 7 files changed, 115 insertions(+), 6 deletions(-) diff --git a/assets/maps/prop.tsj b/assets/maps/prop.tsj index fa16098..89de14b 100644 --- a/assets/maps/prop.tsj +++ b/assets/maps/prop.tsj @@ -224,7 +224,13 @@ "visible":true, "x":0, "y":0 - } + }, + "properties":[ + { + "name":"hazard", + "type":"bool", + "value":true + }] }, { "id":26, @@ -252,7 +258,13 @@ "visible":true, "x":0, "y":0 - } + }, + "properties":[ + { + "name":"hazard", + "type":"bool", + "value":true + }] }, { "id":27, diff --git a/src/engine/component/tilelayer_component.h b/src/engine/component/tilelayer_component.h index cb0d6d1..1a494e9 100644 --- a/src/engine/component/tilelayer_component.h +++ b/src/engine/component/tilelayer_component.h @@ -31,6 +31,7 @@ enum class TileType { SLOPE_2_1, ///< @brief 斜坡瓦片,高度:左1/2右1 SLOPE_1_2, ///< @brief 斜坡瓦片,高度:左1 右1/2 SLOPE_2_0, ///< @brief 斜坡瓦片,高度:左1/2右0 + HAZARD, ///< @brief 危险瓦片(例如火焰、尖刺等) // 未来补充其它类型 }; diff --git a/src/engine/physics/physics_engine.cpp b/src/engine/physics/physics_engine.cpp index 52dd0d6..fe950ed 100644 --- a/src/engine/physics/physics_engine.cpp +++ b/src/engine/physics/physics_engine.cpp @@ -5,9 +5,9 @@ #include "../component/collider_component.h" #include "../component/tilelayer_component.h" #include "../object/game_object.h" -#include +#include #include -#include "glm/common.hpp" +#include namespace engine::physics { @@ -37,8 +37,9 @@ void PhysicsEngine::unregisterCollisionLayer(engine::component::TileLayerCompone } void PhysicsEngine::update(float delta_time) { - // 每帧开始时先清空碰撞对列表 + // 每帧开始时先清空碰撞对列表和瓦片触发事件列表 collision_pairs_.clear(); + tile_trigger_events_.clear(); // 遍历所有注册的物理组件 for (auto* pc : components_) { @@ -66,6 +67,9 @@ void PhysicsEngine::update(float delta_time) { } // 处理对象间碰撞 checkObjectCollisions(); + + // 检测瓦片触发事件 (检测前已经处理完位移) + checkTileTriggers(); } void PhysicsEngine::checkObjectCollisions() @@ -118,7 +122,7 @@ void PhysicsEngine::resolveTileCollisions(engine::component::PhysicsComponent* p if (world_aabb.size.x <= 0.0f || world_aabb.size.y <= 0.0f) return; // -- 检查结束, 正式开始处理 -- - auto tolerance = 1.0f; // 检查右边缘和下边缘时,需要减1像素,否则会检查到下一行/列的瓦片 + constexpr float tolerance = 1.0f; // 检查右边缘和下边缘时,需要减1像素,否则会检查到下一行/列的瓦片 auto ds = pc->velocity_ * delta_time; // 计算物体在delta_time内的位移 auto new_obj_pos = obj_pos + ds; // 计算物体在delta_time后的新位置 @@ -323,6 +327,52 @@ float PhysicsEngine::getTileHeightAtWidth(float width, engine::component::TileTy } } +void PhysicsEngine::checkTileTriggers() +{ + for (auto* pc : components_) { + if (!pc || !pc->isEnabled()) continue; // 检查组件是否有效和启用 + auto* obj = pc->getOwner(); + if (!obj) continue; + auto* cc = obj->getComponent(); + if (!cc || !cc->isActive() || cc->isTrigger()) continue; // 如果游戏对象本就是触发器,则不需要检查瓦片触发事件 + + // 获取物体的世界AABB + auto world_aabb = cc->getWorldAABB(); + + // 使用 set 来跟踪循环遍历中已经触发过的瓦片类型,防止重复添加(例如,玩家同时踩到两个尖刺,只需要受到一次伤害) + std::set triggers_set; + + // 遍历所有注册的碰撞瓦片层分别进行检测 + for (auto* layer : collision_tile_layers_) { + if (!layer) continue; + auto tile_size = layer->getTileSize(); + constexpr float tolerance = 1.0f; // 检查右边缘和下边缘时,需要减1像素,否则会检查到下一行/列的瓦片 + // 获取瓦片坐标范围 + auto start_x = static_cast(floor(world_aabb.position.x / tile_size.x)); + auto end_x = static_cast(ceil((world_aabb.position.x + world_aabb.size.x - tolerance) / tile_size.x)); + auto start_y = static_cast(floor(world_aabb.position.y / tile_size.y)); + auto end_y = static_cast(ceil((world_aabb.position.y + world_aabb.size.y - tolerance) / tile_size.y)); + + // 遍历瓦片坐标范围进行检测 + for (int x = start_x; x < end_x; ++x) { + for (int y = start_y; y < end_y; ++y) { + auto tile_type = layer->getTileTypeAt({x, y}); + // 未来可以添加更多触发器类型的瓦片,目前只有 HAZARD 类型 + if (tile_type == engine::component::TileType::HAZARD) { + triggers_set.insert(tile_type); // 记录触发事件,set 保证每个瓦片类型只记录一次 + } + } + } + // 遍历触发事件集合,添加到 tile_trigger_events_ 中 + for (const auto& type : triggers_set) { + tile_trigger_events_.emplace_back(obj, type); + spdlog::trace("tile_trigger_events_中 添加了 GameObject {} 和瓦片触发类型: {}", + obj->getName(), static_cast(type)); + } + } + } +} + void PhysicsEngine::applyWorldBounds(engine::component::PhysicsComponent *pc) { if (!pc || !world_bounds_) return; diff --git a/src/engine/physics/physics_engine.h b/src/engine/physics/physics_engine.h index cd52179..2ab7351 100644 --- a/src/engine/physics/physics_engine.h +++ b/src/engine/physics/physics_engine.h @@ -30,6 +30,8 @@ private: /// @brief 存储本帧发生的 GameObject 碰撞对 (每次 update 开始时清空) std::vector> collision_pairs_; + /// @brief 存储本帧发生的瓦片触发事件 (GameObject*, 触发的瓦片类型, 每次 update 开始时清空) + std::vector> tile_trigger_events_; public: PhysicsEngine() = default; @@ -60,6 +62,10 @@ public: const std::vector>& getCollisionPairs() const { return collision_pairs_; }; + /// @brief 获取本帧检测到的所有瓦片触发事件。(此列表在每次 update 开始时清空) + const std::vector>& getTileTriggerEvents() const { + return tile_trigger_events_; + }; private: void checkObjectCollisions(); ///< @brief 检测并处理对象之间的碰撞,并记录需要游戏逻辑处理的碰撞对。 @@ -77,6 +83,11 @@ private: * @return 瓦片上对应高度(从瓦片下侧起算)。 */ float getTileHeightAtWidth(float width, engine::component::TileType type, glm::vec2 tile_size); + + /** + * @brief 检测所有游戏对象与瓦片层的触发器类型瓦片碰撞,并记录触发事件。(位移处理完毕后再调用) + */ + void checkTileTriggers(); }; } // namespace engine::physics \ No newline at end of file diff --git a/src/engine/scene/level_loader.cpp b/src/engine/scene/level_loader.cpp index 2fbcf24..5c69ab0 100644 --- a/src/engine/scene/level_loader.cpp +++ b/src/engine/scene/level_loader.cpp @@ -219,6 +219,10 @@ void LevelLoader::loadObjectLayer(const nlohmann::json& layer_json, Scene& scene if (tag) { game_object->setTag(tag.value()); } + // 如果是危险瓦片,且没有手动设置标签,则自动设置标签为 "hazard" + else if (tile_info.type == engine::component::TileType::HAZARD) { + game_object->setTag("hazard"); + } // 获取重力信息并设置 auto gravity = getTileProperty(tile_json, "gravity"); @@ -360,6 +364,10 @@ engine::component::TileType LevelLoader::getTileType(const nlohmann::json &tile_ auto is_unisolid = property.value("value", false); return is_unisolid ? engine::component::TileType::UNISOLID : engine::component::TileType::NORMAL; } + else if (property.contains("name") && property["name"] == "hazard") { + auto is_hazard = property.value("value", false); + return is_hazard ? engine::component::TileType::HAZARD : engine::component::TileType::NORMAL; + } // TODO: 可以在这里添加更多的自定义属性处理逻辑 } } diff --git a/src/game/scene/game_scene.cpp b/src/game/scene/game_scene.cpp index 009dc27..d453336 100644 --- a/src/game/scene/game_scene.cpp +++ b/src/game/scene/game_scene.cpp @@ -55,6 +55,7 @@ void GameScene::init() { void GameScene::update(float delta_time) { Scene::update(delta_time); handleObjectCollisions(); + handleTileTriggers(); } void GameScene::render() { @@ -190,6 +191,31 @@ void GameScene::handleObjectCollisions() } else if (obj2->getName() == "player" && obj1->getTag() == "item") { PlayerVSItemCollision(obj2, obj1); } + // 处理玩家与"hazard"对象碰撞 + else if (obj1->getName() == "player" && obj2->getTag() == "hazard") { + obj1->getComponent()->takeDamage(1); + spdlog::debug("玩家 {} 受到了 HAZARD 对象伤害", obj1->getName()); + } else if (obj2->getName() == "player" && obj1->getTag() == "hazard") { + obj2->getComponent()->takeDamage(1); + spdlog::debug("玩家 {} 受到了 HAZARD 对象伤害", obj2->getName()); + } + } +} + +void GameScene::handleTileTriggers() +{ + const auto& tile_trigger_events = context_.getPhysicsEngine().getTileTriggerEvents(); + for (const auto& event : tile_trigger_events) { + auto* obj = event.first; // 瓦片触发事件的对象 + auto tile_type = event.second; // 瓦片类型 + if (tile_type == engine::component::TileType::HAZARD) { + // 玩家碰到到危险瓦片,受伤 + if (obj->getName() == "player") { + obj->getComponent()->takeDamage(1); + spdlog::debug("玩家 {} 受到了 HAZARD 瓦片伤害", obj->getName()); + } + // TODO: 其他对象类型的处理,目前让敌人无视瓦片伤害 + } } } diff --git a/src/game/scene/game_scene.h b/src/game/scene/game_scene.h index 877eeba..7ffcc2c 100644 --- a/src/game/scene/game_scene.h +++ b/src/game/scene/game_scene.h @@ -32,6 +32,7 @@ private: [[nodiscard]] bool initEnemyAndItem(); ///< @brief 初始化敌人和道具 void handleObjectCollisions(); ///< @brief 处理游戏对象间的碰撞逻辑(从PhysicsEngine获取信息) + void handleTileTriggers(); ///< @brief 处理瓦片触发事件(从PhysicsEngine获取信息) void PlayerVSEnemyCollision(engine::object::GameObject* player, engine::object::GameObject* enemy); ///< @brief 玩家与敌人碰撞处理 void PlayerVSItemCollision(engine::object::GameObject* player, engine::object::GameObject* item); ///< @brief 玩家与道具碰撞处理