完成玩家与其他对象交互-2

This commit is contained in:
Ziyu
2025-06-16 14:44:47 +08:00
parent e5399af1d6
commit 5e4f6c587f
7 changed files with 115 additions and 6 deletions
+14 -2
View File
@@ -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,
@@ -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 危险瓦片(例如火焰、尖刺等)
// 未来补充其它类型
};
+54 -4
View File
@@ -5,9 +5,9 @@
#include "../component/collider_component.h"
#include "../component/tilelayer_component.h"
#include "../object/game_object.h"
#include <algorithm>
#include <set>
#include <spdlog/spdlog.h>
#include "glm/common.hpp"
#include <glm/common.hpp>
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<engine::component::ColliderComponent>();
if (!cc || !cc->isActive() || cc->isTrigger()) continue; // 如果游戏对象本就是触发器,则不需要检查瓦片触发事件
// 获取物体的世界AABB
auto world_aabb = cc->getWorldAABB();
// 使用 set 来跟踪循环遍历中已经触发过的瓦片类型,防止重复添加(例如,玩家同时踩到两个尖刺,只需要受到一次伤害)
std::set<engine::component::TileType> 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<int>(floor(world_aabb.position.x / tile_size.x));
auto end_x = static_cast<int>(ceil((world_aabb.position.x + world_aabb.size.x - tolerance) / tile_size.x));
auto start_y = static_cast<int>(floor(world_aabb.position.y / tile_size.y));
auto end_y = static_cast<int>(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<int>(type));
}
}
}
}
void PhysicsEngine::applyWorldBounds(engine::component::PhysicsComponent *pc)
{
if (!pc || !world_bounds_) return;
+11
View File
@@ -30,6 +30,8 @@ private:
/// @brief 存储本帧发生的 GameObject 碰撞对 (每次 update 开始时清空)
std::vector<std::pair<engine::object::GameObject*, engine::object::GameObject*>> collision_pairs_;
/// @brief 存储本帧发生的瓦片触发事件 (GameObject*, 触发的瓦片类型, 每次 update 开始时清空)
std::vector<std::pair<engine::object::GameObject*, engine::component::TileType>> tile_trigger_events_;
public:
PhysicsEngine() = default;
@@ -60,6 +62,10 @@ public:
const std::vector<std::pair<engine::object::GameObject*, engine::object::GameObject*>>& getCollisionPairs() const {
return collision_pairs_;
};
/// @brief 获取本帧检测到的所有瓦片触发事件。(此列表在每次 update 开始时清空)
const std::vector<std::pair<engine::object::GameObject*, engine::component::TileType>>& 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
+8
View File
@@ -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<bool>(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: 可以在这里添加更多的自定义属性处理逻辑
}
}
+26
View File
@@ -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<game::component::PlayerComponent>()->takeDamage(1);
spdlog::debug("玩家 {} 受到了 HAZARD 对象伤害", obj1->getName());
} else if (obj2->getName() == "player" && obj1->getTag() == "hazard") {
obj2->getComponent<game::component::PlayerComponent>()->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<game::component::PlayerComponent>()->takeDamage(1);
spdlog::debug("玩家 {} 受到了 HAZARD 瓦片伤害", obj->getName());
}
// TODO: 其他对象类型的处理,目前让敌人无视瓦片伤害
}
}
}
+1
View File
@@ -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 玩家与道具碰撞处理