完成瓦片层碰撞解析
This commit is contained in:
+4
-2
@@ -10,10 +10,12 @@
|
||||
- 总共有“地图大小”个瓦片,放入容器vector中
|
||||
- 每个瓦片包含数据:Sprite,Type(例如solid类型)
|
||||
- 可能引用多个图块集,因此可先*载入并保存每个图块集的数据*(载入函数),其它瓦片层(以及对象层)也能继续引用
|
||||
- 通过data数组中的gid查找所需信息(查找函数),填充瓦片vector
|
||||
- 通过data数组中的gid查找所需信息(查找函数,能获取自定义属性),填充瓦片vector
|
||||
- 创建包含 TileLayerComponent 的游戏对象(持有瓦片vector)
|
||||
|
||||
- 如果 type : objectgroup 载入对象层
|
||||
- 对象数据在"objects"字段中,每个对象都创建一个游戏对象
|
||||
- 关注的要点:gid查找源图片信息,layer数据中确认目标位置(TransformComponent)信息
|
||||
- 如果没有gid,则代表自己绘制的形状(可能是碰撞盒、触发器等,未来按需处理)
|
||||
- 如果没有gid,则代表自己绘制的形状(可能是碰撞盒、触发器等,未来按需处理)
|
||||
|
||||
- 自定义属性的获取方法:图块集中 获取 "tiles" --> 获取 "properties"
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../core/context.h"
|
||||
#include "../render/renderer.h"
|
||||
#include "../render/camera.h"
|
||||
#include "../physics/physics_engine.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <algorithm>
|
||||
#include <spdlog/spdlog.h>
|
||||
@@ -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<engine::component::TransformComponent>();
|
||||
auto* cc = obj->getComponent<engine::component::ColliderComponent>();
|
||||
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<int>(floor(right_top_x / tile_size.x)); // 获取x方向瓦片坐标
|
||||
// y方向坐标有两个,右上和右下
|
||||
auto tile_y = static_cast<int>(floor(obj_pos.y / tile_size.y));
|
||||
auto tile_type_top = layer->getTileTypeAt({tile_x, tile_y}); // 右上角瓦片类型
|
||||
auto tile_y_bottom = static_cast<int>(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<int>(floor(left_top_x / tile_size.x)); // 获取x方向瓦片坐标
|
||||
// y方向坐标有两个,左上和左下
|
||||
auto tile_y = static_cast<int>(floor(obj_pos.y / tile_size.y));
|
||||
auto tile_type_top = layer->getTileTypeAt({tile_x, tile_y}); // 左上角瓦片类型
|
||||
auto tile_y_bottom = static_cast<int>(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<int>(floor(bottom_left_y / tile_size.y));
|
||||
|
||||
auto tile_x = static_cast<int>(floor(obj_pos.x / tile_size.x));
|
||||
auto tile_type_left = layer->getTileTypeAt({tile_x, tile_y}); // 左下角瓦片类型
|
||||
auto tile_x_right = static_cast<int>(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<int>(floor(top_left_y / tile_size.y));
|
||||
|
||||
auto tile_x = static_cast<int>(floor(obj_pos.x / tile_size.x));
|
||||
auto tile_type_left = layer->getTileTypeAt({tile_x, tile_y}); // 左上角瓦片类型
|
||||
auto tile_x_right = static_cast<int>(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
|
||||
@@ -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<engine::component::PhysicsComponent*> components_; ///< @brief 注册的物理组件容器,非拥有指针
|
||||
std::vector<engine::component::TileLayerComponent*> 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 设置全局重力加速度
|
||||
|
||||
@@ -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<float>(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<float>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
#include <glm/vec2.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
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。
|
||||
|
||||
@@ -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<engine::component::TileLayerComponent>();
|
||||
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<engine::component::PhysicsComponent>();
|
||||
if (!pc) return;
|
||||
if (input_manager.isActionDown("move_left")) {
|
||||
test_object_->getComponent<engine::component::TransformComponent>()->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<engine::component::TransformComponent>()->translate(glm::vec2(1, 0));
|
||||
pc->velocity_.x = 100.0f;
|
||||
} else {
|
||||
pc->velocity_.x *= 0.9f;
|
||||
}
|
||||
if (input_manager.isActionPressed("jump")) {
|
||||
test_object_->getComponent<engine::component::PhysicsComponent>()->setVelocity(glm::vec2(0, -400));
|
||||
pc->velocity_.y = -400.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user