完成瓦片层单向平台与斜坡
This commit is contained in:
@@ -24,6 +24,13 @@ enum class TileType {
|
||||
EMPTY, ///< @brief 空白瓦片
|
||||
NORMAL, ///< @brief 普通瓦片
|
||||
SOLID, ///< @brief 静止可碰撞瓦片
|
||||
UNISOLID, ///< @brief 单向静止可碰撞瓦片
|
||||
SLOPE_0_1, ///< @brief 斜坡瓦片,高度:左0 右1
|
||||
SLOPE_1_0, ///< @brief 斜坡瓦片,高度:左1 右0
|
||||
SLOPE_0_2, ///< @brief 斜坡瓦片,高度:左0 右1/2
|
||||
SLOPE_2_1, ///< @brief 斜坡瓦片,高度:左1/2右1
|
||||
SLOPE_1_2, ///< @brief 斜坡瓦片,高度:左1 右1/2
|
||||
SLOPE_2_0, ///< @brief 斜坡瓦片,高度:左1/2右0
|
||||
// 未来补充其它类型
|
||||
};
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ void PhysicsEngine::update(float delta_time) {
|
||||
|
||||
// 处理瓦片层碰撞(速度和位置的更新移入此函数)
|
||||
resolveTileCollisions(pc, delta_time);
|
||||
|
||||
// 应用世界边界
|
||||
applyWorldBounds(pc);
|
||||
}
|
||||
// 处理对象间碰撞
|
||||
checkObjectCollisions();
|
||||
@@ -136,6 +139,16 @@ void PhysicsEngine::resolveTileCollisions(engine::component::PhysicsComponent* p
|
||||
// 撞墙了!速度归零,x方向移动到贴着墙的位置
|
||||
new_obj_pos.x = tile_x * layer->getTileSize().x - obj_size.x;
|
||||
pc->velocity_.x = 0.0f;
|
||||
} else {
|
||||
// 检测右下角斜坡瓦片
|
||||
auto width_right = new_obj_pos.x + obj_size.x - tile_x * tile_size.x;
|
||||
auto height_right = getTileHeightAtWidth(width_right, tile_type_bottom, tile_size);
|
||||
if (height_right > 0.0f) {
|
||||
// 如果有碰撞(角点的世界y坐标 > 斜坡地面的世界y坐标), 就让物体贴着斜坡表面
|
||||
if (new_obj_pos.y > (tile_y_bottom + 1) * layer->getTileSize().y - obj_size.y - height_right) {
|
||||
new_obj_pos.y = (tile_y_bottom + 1) * layer->getTileSize().y - obj_size.y - height_right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ds.x < 0.0f) {
|
||||
@@ -152,6 +165,15 @@ void PhysicsEngine::resolveTileCollisions(engine::component::PhysicsComponent* p
|
||||
// 撞墙了!速度归零,x方向移动到贴着墙的位置
|
||||
new_obj_pos.x = (tile_x + 1) * layer->getTileSize().x;
|
||||
pc->velocity_.x = 0.0f;
|
||||
} else {
|
||||
// 检测左下角斜坡瓦片
|
||||
auto width_left = new_obj_pos.x - tile_x * tile_size.x;
|
||||
auto height_left = getTileHeightAtWidth(width_left, tile_type_bottom, tile_size);
|
||||
if (height_left > 0.0f) {
|
||||
if (new_obj_pos.y > (tile_y_bottom + 1) * layer->getTileSize().y - obj_size.y - height_left) {
|
||||
new_obj_pos.y = (tile_y_bottom + 1) * layer->getTileSize().y - obj_size.y - height_left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 轴分离碰撞检测:再检查Y方向是否有碰撞 (x方向使用初始值obj_pos.x)
|
||||
@@ -165,10 +187,24 @@ void PhysicsEngine::resolveTileCollisions(engine::component::PhysicsComponent* p
|
||||
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) {
|
||||
if (tile_type_left == engine::component::TileType::SOLID || tile_type_right == engine::component::TileType::SOLID ||
|
||||
tile_type_left == engine::component::TileType::UNISOLID || tile_type_right == engine::component::TileType::UNISOLID) {
|
||||
// 到达地面!速度归零,y方向移动到贴着地面的位置
|
||||
new_obj_pos.y = tile_y * layer->getTileSize().y - obj_size.y;
|
||||
pc->velocity_.y = 0.0f;
|
||||
} else {
|
||||
// 检测斜坡瓦片(下方两个角点都要检测)
|
||||
auto width_left = obj_pos.x - tile_x * tile_size.x;
|
||||
auto width_right = obj_pos.x + obj_size.x - tile_x_right * tile_size.x;
|
||||
auto height_left = getTileHeightAtWidth(width_left, tile_type_left, tile_size);
|
||||
auto height_right = getTileHeightAtWidth(width_right, tile_type_right, tile_size);
|
||||
auto height = glm::max(height_left, height_right); // 找到两个角点的最高点进行检测
|
||||
if (height > 0.0f) { // 说明至少有一个角点处于斜坡瓦片
|
||||
if (new_obj_pos.y > (tile_y + 1) * layer->getTileSize().y - obj_size.y - height) {
|
||||
new_obj_pos.y = (tile_y + 1) * layer->getTileSize().y - obj_size.y - height;
|
||||
pc->velocity_.y = 0.0f; // 只有向下运动时才需要让 y 速度归零
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ds.y < 0.0f) {
|
||||
@@ -239,4 +275,56 @@ void PhysicsEngine::resolveSolidObjectCollisions(engine::object::GameObject* mov
|
||||
}
|
||||
}
|
||||
|
||||
float PhysicsEngine::getTileHeightAtWidth(float width, engine::component::TileType type, glm::vec2 tile_size)
|
||||
{
|
||||
auto rel_x = glm::clamp(width / tile_size.x, 0.0f, 1.0f);
|
||||
switch (type) {
|
||||
case engine::component::TileType::SLOPE_0_1: // 左0 右1
|
||||
return rel_x * tile_size.y;
|
||||
case engine::component::TileType::SLOPE_0_2: // 左0 右1/2
|
||||
return rel_x * tile_size.y * 0.5f;
|
||||
case engine::component::TileType::SLOPE_2_1: // 左1/2右1
|
||||
return rel_x * tile_size.y * 0.5f + tile_size.y * 0.5f;
|
||||
case engine::component::TileType::SLOPE_1_0: // 左1 右0
|
||||
return (1.0f - rel_x) * tile_size.y;
|
||||
case engine::component::TileType::SLOPE_2_0: // 左1/2右0
|
||||
return (1.0f - rel_x) * tile_size.y * 0.5f;
|
||||
case engine::component::TileType::SLOPE_1_2: // 左1 右1/2
|
||||
return (1.0f - rel_x) * tile_size.y * 0.5f + tile_size.y * 0.5f;
|
||||
default:
|
||||
return 0.0f; // 默认返回0,表示没有斜坡
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicsEngine::applyWorldBounds(engine::component::PhysicsComponent *pc)
|
||||
{
|
||||
if (!pc || !world_bounds_) return;
|
||||
|
||||
// 只限定左、上、右边界,不限定下边界,以碰撞盒作为判断依据
|
||||
auto* obj = pc->getOwner();
|
||||
auto* cc = obj->getComponent<engine::component::ColliderComponent>();
|
||||
auto* tc = obj->getComponent<engine::component::TransformComponent>();
|
||||
auto world_aabb = cc->getWorldAABB();
|
||||
auto obj_pos = world_aabb.position;
|
||||
auto obj_size = world_aabb.size;
|
||||
|
||||
// 检查左边界
|
||||
if (obj_pos.x < world_bounds_->position.x) {
|
||||
pc->velocity_.x = 0.0f;
|
||||
obj_pos.x = world_bounds_->position.x;
|
||||
}
|
||||
// 检查上边界
|
||||
if (obj_pos.y < world_bounds_->position.y) {
|
||||
pc->velocity_.y = 0.0f;
|
||||
obj_pos.y = world_bounds_->position.y;
|
||||
}
|
||||
// 检查右边界
|
||||
if (obj_pos.x + obj_size.x > world_bounds_->position.x + world_bounds_->size.x) {
|
||||
pc->velocity_.x = 0.0f;
|
||||
obj_pos.x = world_bounds_->position.x + world_bounds_->size.x - obj_size.x;
|
||||
}
|
||||
// 更新物体位置(使用translate方法,新位置 - 旧位置)
|
||||
tc->translate(obj_pos - world_aabb.position);
|
||||
}
|
||||
|
||||
} // namespace engine::physics
|
||||
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
#include "../utils/math.h"
|
||||
#include <vector>
|
||||
#include <utility> // for std::pair
|
||||
#include "glm/vec2.hpp"
|
||||
#include <optional>
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
namespace engine::component {
|
||||
class PhysicsComponent;
|
||||
class TileLayerComponent;
|
||||
enum class TileType;
|
||||
}
|
||||
|
||||
namespace engine::object {
|
||||
@@ -23,6 +26,7 @@ private:
|
||||
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 最大速度 (像素/秒)
|
||||
std::optional<engine::utils::Rect> world_bounds_; ///< @brief 世界边界,用于限制物体移动范围
|
||||
|
||||
/// @brief 存储本帧发生的 GameObject 碰撞对 (每次 update 开始时清空)
|
||||
std::vector<std::pair<engine::object::GameObject*, engine::object::GameObject*>> collision_pairs_;
|
||||
@@ -50,6 +54,8 @@ public:
|
||||
const glm::vec2& getGravity() const { return gravity_; } ///< @brief 获取当前的全局重力加速度
|
||||
void setMaxSpeed(float max_speed) { max_speed_ = max_speed; } ///< @brief 设置最大速度
|
||||
float getMaxSpeed() const { return max_speed_; } ///< @brief 获取当前的最大速度
|
||||
void setWorldBounds(const engine::utils::Rect& world_bounds) { world_bounds_ = world_bounds; } ///< @brief 设置世界边界
|
||||
const std::optional<engine::utils::Rect>& getWorldBounds() const { return world_bounds_; } ///< @brief 获取世界边界
|
||||
/// @brief 获取本帧检测到的所有 GameObject 碰撞对。(此列表在每次 update 开始时清空)
|
||||
const std::vector<std::pair<engine::object::GameObject*, engine::object::GameObject*>>& getCollisionPairs() const {
|
||||
return collision_pairs_;
|
||||
@@ -61,6 +67,16 @@ private:
|
||||
void resolveTileCollisions(engine::component::PhysicsComponent* pc, float delta_time);
|
||||
/// @brief 处理可移动物体与SOLID物体的碰撞。
|
||||
void resolveSolidObjectCollisions(engine::object::GameObject* move_obj, engine::object::GameObject* solid_obj);
|
||||
void applyWorldBounds(engine::component::PhysicsComponent* pc); ///< @brief 应用世界边界,限制物体移动范围
|
||||
|
||||
/**
|
||||
* @brief 根据瓦片类型和指定宽度x坐标,计算瓦片上对应y坐标。
|
||||
* @param width 从瓦片左侧起算的宽度。
|
||||
* @param type 瓦片类型。
|
||||
* @param tile_size 瓦片尺寸。
|
||||
* @return 瓦片上对应高度(从瓦片下侧起算)。
|
||||
*/
|
||||
float getTileHeightAtWidth(float width, engine::component::TileType type, glm::vec2 tile_size);
|
||||
};
|
||||
|
||||
} // namespace engine::physics
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "camera.h"
|
||||
#include "../utils/math.h"
|
||||
#include "../component/transform_component.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace engine::render {
|
||||
@@ -14,9 +15,27 @@ void Camera::setPosition(const glm::vec2& position) {
|
||||
clampPosition();
|
||||
}
|
||||
|
||||
void Camera::update(float /* delta_time */)
|
||||
void Camera::update(float delta_time)
|
||||
{
|
||||
// TODO 自动跟随目标
|
||||
if (target_ == nullptr) return;
|
||||
glm::vec2 target_pos = target_->getPosition();
|
||||
glm::vec2 desired_position = target_pos - viewport_size_ / 2.0f; // 计算目标位置 (让目标位于视口中心)
|
||||
|
||||
// 计算当前位置与目标位置的距离
|
||||
auto distance_ = glm::distance(position_, desired_position);
|
||||
constexpr float SNAP_THRESHOLD = 1.0f; // 设置一个距离阈值 (constexpr: 编译时常量,避免每次调用都计算)
|
||||
|
||||
if (distance_ < SNAP_THRESHOLD) {
|
||||
// 如果距离小于阈值,直接吸附到目标位置
|
||||
position_ = desired_position;
|
||||
} else {
|
||||
// 否则,使用线性插值平滑移动 glm::mix(a,b,t): 在向量 a 和 b 之间进行插值,t 是插值因子,范围在0到1之间。
|
||||
// 公式: (b-a)*t + a; t = 0 时结果为 a,t = 1 时结果为 b
|
||||
position_ = glm::mix(position_, desired_position, smooth_speed_ * delta_time);
|
||||
position_ = glm::vec2(glm::round(position_.x), glm::round(position_.y)); // 四舍五入到整数,省略的话偶尔会出现画面割裂
|
||||
}
|
||||
|
||||
clampPosition();
|
||||
}
|
||||
|
||||
void Camera::move(const glm::vec2 &offset)
|
||||
@@ -31,6 +50,16 @@ void Camera::setLimitBounds(const engine::utils::Rect& bounds)
|
||||
clampPosition(); // 设置边界后,立即应用限制
|
||||
}
|
||||
|
||||
void Camera::setTarget(engine::component::TransformComponent *target)
|
||||
{
|
||||
target_ = target;
|
||||
}
|
||||
|
||||
engine::component::TransformComponent *Camera::getTarget() const
|
||||
{
|
||||
return target_;
|
||||
}
|
||||
|
||||
const glm::vec2& Camera::getPosition() const {
|
||||
return position_;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
#include "../utils/math.h"
|
||||
#include <optional>
|
||||
|
||||
namespace engine::component {
|
||||
class TransformComponent;
|
||||
}
|
||||
|
||||
namespace engine::render {
|
||||
|
||||
/**
|
||||
@@ -13,7 +17,9 @@ private:
|
||||
glm::vec2 viewport_size_; ///< @brief 视口大小(屏幕大小)
|
||||
glm::vec2 position_; ///< @brief 相机左上角的世界坐标
|
||||
std::optional<engine::utils::Rect> limit_bounds_; ///< @brief 限制相机的移动范围,空值表示不限制
|
||||
|
||||
float smooth_speed_ = 5.0f; ///< @brief 相机移动的平滑速度
|
||||
engine::component::TransformComponent* target_ = nullptr; ///< @brief 跟随目标变换组件,空值表示不跟随
|
||||
|
||||
public:
|
||||
|
||||
Camera(const glm::vec2& viewport_size, const glm::vec2& position = glm::vec2(0.0f, 0.0f), const std::optional<engine::utils::Rect> limit_bounds = std::nullopt);
|
||||
@@ -27,10 +33,12 @@ public:
|
||||
|
||||
void setPosition(const glm::vec2& position); ///< @brief 设置相机位置
|
||||
void setLimitBounds(const engine::utils::Rect& bounds); ///< @brief 设置限制相机的移动范围
|
||||
void setTarget(engine::component::TransformComponent* target); ///< @brief 设置跟随目标变换组件
|
||||
|
||||
const glm::vec2& getPosition() const; ///< @brief 获取相机位置
|
||||
std::optional<engine::utils::Rect> getLimitBounds() const; ///< @brief 获取限制相机的移动范围
|
||||
glm::vec2 getViewportSize() const; ///< @brief 获取视口大小
|
||||
engine::component::TransformComponent* getTarget() const; ///< @brief 获取跟随目标变换组件
|
||||
|
||||
// 禁用拷贝和移动语义
|
||||
Camera(const Camera&) = delete;
|
||||
|
||||
@@ -261,6 +261,29 @@ engine::component::TileType LevelLoader::getTileType(const nlohmann::json &tile_
|
||||
auto is_solid = property.value("value", false);
|
||||
return is_solid ? engine::component::TileType::SOLID : engine::component::TileType::NORMAL;
|
||||
}
|
||||
else if (property.contains("name") && property["name"] == "slope") {
|
||||
auto slope_type = property.value("value", "");
|
||||
if (slope_type == "0_1") {
|
||||
return engine::component::TileType::SLOPE_0_1;
|
||||
} else if (slope_type == "1_0") {
|
||||
return engine::component::TileType::SLOPE_1_0;
|
||||
} else if (slope_type == "0_2") {
|
||||
return engine::component::TileType::SLOPE_0_2;
|
||||
} else if (slope_type == "2_0") {
|
||||
return engine::component::TileType::SLOPE_2_0;
|
||||
} else if (slope_type == "2_1") {
|
||||
return engine::component::TileType::SLOPE_2_1;
|
||||
} else if (slope_type == "1_2") {
|
||||
return engine::component::TileType::SLOPE_1_2;
|
||||
} else {
|
||||
spdlog::error("未知的斜坡类型: {}", slope_type);
|
||||
return engine::component::TileType::NORMAL;
|
||||
}
|
||||
}
|
||||
else if (property.contains("name") && property["name"] == "unisolid") {
|
||||
auto is_unisolid = property.value("value", false);
|
||||
return is_unisolid ? engine::component::TileType::UNISOLID : engine::component::TileType::NORMAL;
|
||||
}
|
||||
// TODO: 可以在这里添加更多的自定义属性处理逻辑
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../object/game_object.h"
|
||||
#include "../core/context.h"
|
||||
#include "../physics/physics_engine.h"
|
||||
#include "../render/camera.h"
|
||||
#include <algorithm> // for std::remove_if
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
@@ -25,6 +26,8 @@ void Scene::update(float delta_time) {
|
||||
|
||||
// 先更新物理引擎
|
||||
context_.getPhysicsEngine().update(delta_time);
|
||||
// 更新相机
|
||||
context_.getCamera().update(delta_time);
|
||||
|
||||
// 更新所有游戏对象,并删除需要移除的对象
|
||||
for (auto it = game_objects_.begin(); it != game_objects_.end();) {
|
||||
|
||||
@@ -42,6 +42,18 @@ void GameScene::init() {
|
||||
spdlog::error("未找到玩家对象");
|
||||
return;
|
||||
}
|
||||
// 相机跟随玩家
|
||||
auto* player_transform = player_->getComponent<engine::component::TransformComponent>();
|
||||
if (player_transform) {
|
||||
context_.getCamera().setTarget(player_transform);
|
||||
}
|
||||
|
||||
// 设置相机边界
|
||||
auto world_size = main_layer->getComponent<engine::component::TileLayerComponent>()->getWorldSize();
|
||||
context_.getCamera().setLimitBounds(engine::utils::Rect(glm::vec2(0.0f), world_size));
|
||||
|
||||
// 设置世界边界
|
||||
context_.getPhysicsEngine().setWorldBounds(engine::utils::Rect(glm::vec2(0.0f), world_size));
|
||||
|
||||
Scene::init();
|
||||
spdlog::trace("GameScene 初始化完成。");
|
||||
|
||||
Reference in New Issue
Block a user