From a03a5cdf74413b97070263b0fc44cf72073213ac Mon Sep 17 00:00:00 2001 From: Ziyu Date: Thu, 12 Jun 2025 10:47:38 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=85=B3=E5=8D=A1=E5=88=87?= =?UTF-8?q?=E6=8D=A2-=E5=85=B1=E4=BA=AB=E6=B8=B8=E6=88=8F=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + assets/save.json | 7 +++ src/engine/core/game_app.cpp | 2 +- src/game/data/session_data.cpp | 104 +++++++++++++++++++++++++++++++++ src/game/data/session_data.h | 59 +++++++++++++++++++ src/game/scene/game_scene.cpp | 55 ++++++++++++++--- src/game/scene/game_scene.h | 13 ++++- src/main.cpp | 2 +- 8 files changed, 231 insertions(+), 12 deletions(-) create mode 100644 assets/save.json create mode 100644 src/game/data/session_data.cpp create mode 100644 src/game/data/session_data.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ba0fae..95e8648 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,7 @@ add_executable(${TARGET} src/game/component/ai/patrol_behavior.cpp src/game/component/ai/updown_behavior.cpp src/game/component/ai/jump_behavior.cpp + src/game/data/session_data.cpp ) # 链接库 diff --git a/assets/save.json b/assets/save.json new file mode 100644 index 0000000..8207e84 --- /dev/null +++ b/assets/save.json @@ -0,0 +1,7 @@ +{ + "high_score": 20, + "level_health": 2, + "level_score": 20, + "map_path": "assets/maps/level2.tmj", + "max_health": 3 +} \ No newline at end of file diff --git a/src/engine/core/game_app.cpp b/src/engine/core/game_app.cpp index f8d5ad7..cd760bd 100644 --- a/src/engine/core/game_app.cpp +++ b/src/engine/core/game_app.cpp @@ -61,7 +61,7 @@ bool GameApp::init() { if (!initSceneManager()) return false; // 创建第一个场景并压入栈 - auto scene = std::make_unique("level1", *context_, *scene_manager_); + auto scene = std::make_unique(*context_, *scene_manager_); scene_manager_->requestPushScene(std::move(scene)); is_running_ = true; diff --git a/src/game/data/session_data.cpp b/src/game/data/session_data.cpp new file mode 100644 index 0000000..67d9846 --- /dev/null +++ b/src/game/data/session_data.cpp @@ -0,0 +1,104 @@ +#include "session_data.h" +#include +#include +#include +#include + +namespace game::data { + +void SessionData::setCurrentHealth(int health) { + // 将生命值限制在 0 和 max_health_ 之间 + current_health_ = glm::clamp(health, 0, max_health_); +} + +void SessionData::setMaxHealth(int max_health) { + if (max_health > 0) { + max_health_ = max_health; + // 确保当前生命值不超过新的最大生命值 + setCurrentHealth(current_health_); + } else { + spdlog::warn("尝试将最大生命值设置为非正数: {}", max_health); + } +} + +void SessionData::addScore(int score_to_add) { + current_score_ += score_to_add; + setHighScore(glm::max(high_score_, current_score_)); // 如果当前分数超过最高分,则更新最高分 +} + +void SessionData::reset() { + current_health_ = max_health_; + current_score_ = 0; + level_health_ = 3; + level_score_ = 0; + map_path_ = "assets/maps/level1.tmj"; + spdlog::info("SessionData reset."); +} + +void SessionData::setNextLevel(const std::string &map_path) +{ + map_path_ = map_path; + level_health_ = current_health_; + level_score_ = current_score_; +} + +bool SessionData::saveToFile(const std::string& filename) const { + nlohmann::json j; + try { + // 将成员变量序列化到 JSON 对象中 + j["level_score"] = level_score_; + j["level_health"] = level_health_; + j["max_health"] = max_health_; + j["high_score"] = high_score_; + j["map_path"] = map_path_; + + // 打开文件进行写入 + std::ofstream ofs(filename); + if (!ofs.is_open()) { + spdlog::error("无法打开存档文件进行写入: {}", filename); + return false; + } + + // 将 JSON 对象写入文件(使用4个空格进行美化输出) + ofs << j.dump(4); + ofs.close(); // 确保文件关闭 + + spdlog::info("游戏数据成功存储到: {}", filename); + return true; + } catch (const std::exception& e) { + spdlog::error("存档时出现错误 {}: {}", filename, e.what()); + return false; + } +} + +bool SessionData::loadFromFile(const std::string& filename) { + try { + // 打开文件进行读取 + std::ifstream ifs(filename); + if (!ifs.is_open()) { + spdlog::warn("读档时找不到文件: {}", filename); + // 如果存档文件不存在,这不一定是错误 + return false; + } + + // 从文件解析 JSON 数据 + nlohmann::json j; + ifs >> j; + ifs.close(); // 读取完成后关闭文件 + + current_score_ = level_score_ = j.value("level_score", 0); + current_health_ = level_health_ = j.value("level_health", 3); + max_health_ = j.value("max_health", 3); // 使用合理的默认值 + high_score_ = j.value("high_score", 0); + map_path_ = j.value("map_path", "assets/maps/level1.tmj"); // 默认起始地图 + + spdlog::info("游戏数据成功加载: {}", filename); + return true; + } catch (const std::exception& e) { + spdlog::error("读档时出现错误 {}: {}", filename, e.what()); + reset(); + return false; + } +} + +} // namespace game::state \ No newline at end of file diff --git a/src/game/data/session_data.h b/src/game/data/session_data.h new file mode 100644 index 0000000..4f1e0ca --- /dev/null +++ b/src/game/data/session_data.h @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include + +namespace game::data { + +/** + * @brief 管理不同游戏场景之间的游戏状态 + * + * 存储玩家生命值、分数、当前关卡等信息, + * 使这些数据在场景切换时能够保持。 + */ +class SessionData final { +private: + int current_health_ = 3; + int max_health_ = 3; + int current_score_ = 0; + int high_score_ = 0; + + int level_health_ = 3; ///< @brief 进入关卡时的生命值(读/存档用) + int level_score_ = 0; ///< @brief 进入关卡时的得分(读/存档用) + std::string map_path_ = "assets/maps/level1.tmj"; + +public: + SessionData() = default; + ~SessionData() = default; + + // 删除复制和移动操作以防止意外复制 + SessionData(const SessionData&) = delete; + SessionData& operator=(const SessionData&) = delete; + SessionData(SessionData&&) = delete; + SessionData& operator=(SessionData&&) = delete; + + // --- Getters --- + int getCurrentHealth() const { return current_health_; } + int getMaxHealth() const { return max_health_; } + int getCurrentScore() const { return current_score_; } + int getHighScore() const { return high_score_; } + int getLevelHealth() const { return level_health_; } + int getLevelScore() const { return level_score_; } + const std::string& getMapPath() const { return map_path_; } + + // --- Setters --- + void setCurrentHealth(int health); + void setMaxHealth(int max_health); + void addScore(int score_to_add); + void setHighScore(int high_score) { high_score_= high_score; } + void setLevelHealth(int level_health) {level_health_ = level_health; } + void setLevelScore(int level_score) {level_score_ = level_score; } + void setMapPath(const std::string& map_path) { map_path_ = map_path; } + + void reset(); ///< @brief 重置游戏数据以准备开始新游戏(保留最高分) + void setNextLevel(const std::string& map_path); ///< @brief 设置下一个场景信息(地图、关卡开始时的得分生命) + bool saveToFile(const std::string& filename) const; ///< @brief 将当前游戏数据保存到JSON文件(存档) + bool loadFromFile(const std::string& filename); ///< @brief 从JSON文件中读取游戏数据(读档) +}; + +} // namespace game::state \ No newline at end of file diff --git a/src/game/scene/game_scene.cpp b/src/game/scene/game_scene.cpp index bb884c4..5714068 100644 --- a/src/game/scene/game_scene.cpp +++ b/src/game/scene/game_scene.cpp @@ -20,14 +20,20 @@ #include "../component/ai/patrol_behavior.h" #include "../component/ai/updown_behavior.h" #include "../component/ai/jump_behavior.h" +#include "../data/session_data.h" #include #include namespace game::scene { -// 构造函数:调用基类构造函数 -GameScene::GameScene(const std::string& name, engine::core::Context& context, engine::scene::SceneManager& scene_manager) - : Scene(name, context, scene_manager) { +GameScene::GameScene(engine::core::Context& context, + engine::scene::SceneManager& scene_manager, + std::shared_ptr data) + : Scene("GameScene", context, scene_manager), game_session_data_(std::move(data)) { + if (!game_session_data_) { // 如果没有传入SessionData,则创建一个默认的 + game_session_data_ = std::make_shared(); + spdlog::info("未提供 SessionData,使用默认值。"); + } spdlog::trace("GameScene 构造完成。"); } @@ -76,6 +82,7 @@ void GameScene::render() { void GameScene::handleInput() { Scene::handleInput(); + testSaveAndLoad(); } void GameScene::clean() { @@ -86,7 +93,7 @@ bool GameScene::initLevel() { // 加载关卡(level_loader通常加载完成后即可销毁,因此不存为成员变量) engine::scene::LevelLoader level_loader; - auto level_path = levelNameToPath(scene_name_); + auto level_path = game_session_data_->getMapPath(); if (!level_loader.loadLevel(level_path, *this)){ spdlog::error("关卡加载失败"); return false; @@ -227,7 +234,7 @@ void GameScene::handleTileTriggers() if (tile_type == engine::component::TileType::HAZARD) { // 玩家碰到到危险瓦片,受伤 if (obj->getName() == "player") { - obj->getComponent()->takeDamage(1); + handlePlayerDamage(1); spdlog::debug("玩家 {} 受到了 HAZARD 瓦片伤害", obj->getName()); } // TODO: 其他对象类型的处理,目前让敌人无视瓦片伤害 @@ -235,6 +242,20 @@ void GameScene::handleTileTriggers() } } +void GameScene::handlePlayerDamage(int damage) +{ + auto player_component = player_->getComponent(); + if (!player_component->takeDamage(damage)) { // 没有受伤,直接返回 + return; + } + if (player_component->isDead()) { + spdlog::info("玩家 {} 死亡", player_->getName()); + // TODO: 可能的死亡逻辑处理 + } + // 更新游戏数据(生命值) + game_session_data_->setCurrentHealth(player_component->getHealthComponent()->getCurrentHealth()); +} + void GameScene::playerVSEnemyCollision(engine::object::GameObject *player, engine::object::GameObject *enemy) { // --- 踩踏判断逻辑:1. 玩家中心点在敌人上方 2. 重叠区域:overlap.x > overlap.y @@ -262,12 +283,13 @@ void GameScene::playerVSEnemyCollision(engine::object::GameObject *player, engin player->getComponent()->velocity_.y = -300.0f; // 向上跳起 // 播放音效 (此音效完全可以放在玩家的音频组件中,这里示例另一种用法:直接用AudioPlayer播放,传入文件路径) context_.getAudioPlayer().playSound("assets/audio/punch2a.mp3"); + // 加分 + game_session_data_->addScore(10); } // 踩踏判断失败,玩家受伤 else { spdlog::info("敌人 {} 对玩家 {} 造成伤害", enemy->getName(), player->getName()); - player->getComponent()->takeDamage(1); - // TODO: 其他受伤逻辑 + handlePlayerDamage(1); } } @@ -276,7 +298,7 @@ void GameScene::playerVSItemCollision(engine::object::GameObject * player, engin if (item->getName() == "fruit") { player->getComponent()->heal(1); // 加血 } else if (item->getName() == "gem") { - //TODO: 加分 + game_session_data_->addScore(5); } item->setNeedRemove(true); // 标记道具为待删除状态 auto item_aabb = item->getComponent()->getWorldAABB(); @@ -287,7 +309,9 @@ void GameScene::playerVSItemCollision(engine::object::GameObject * player, engin void GameScene::toNextLevel(engine::object::GameObject *trigger) { auto scene_name = trigger->getName(); - auto next_scene = std::make_unique(scene_name, context_, scene_manager_); + auto map_path = levelNameToPath(scene_name); + game_session_data_->setNextLevel(map_path); // 设置下一个关卡信息 + auto next_scene = std::make_unique(context_, scene_manager_, game_session_data_); scene_manager_.requestReplaceScene(std::move(next_scene)); } @@ -327,4 +351,17 @@ void GameScene::createEffect(const glm::vec2& center_pos, const std::string &tag spdlog::debug("创建特效: {}", tag); } +void GameScene::testSaveAndLoad() +{ + auto input_manager = context_.getInputManager(); + if (input_manager.isActionPressed("attack")) { + game_session_data_->saveToFile("assets/save.json"); + } + if (input_manager.isActionPressed("pause")) { + game_session_data_->loadFromFile("assets/save.json"); + spdlog::info("当前生命值: {}", game_session_data_->getCurrentHealth()); + spdlog::info("当前得分: {}", game_session_data_->getCurrentScore()); + } +} + } // namespace game::scene \ No newline at end of file diff --git a/src/game/scene/game_scene.h b/src/game/scene/game_scene.h index d712f56..0f3dd13 100644 --- a/src/game/scene/game_scene.h +++ b/src/game/scene/game_scene.h @@ -8,16 +8,23 @@ namespace engine::object { class GameObject; } +namespace game::data { + class SessionData; +} + namespace game::scene { /** * @brief 主要的游戏场景,包含玩家、敌人、关卡元素等。 */ class GameScene final: public engine::scene::Scene { + std::shared_ptr game_session_data_; ///< @brief 场景间共享数据,因此用shared_ptr engine::object::GameObject* player_ = nullptr; ///< @brief 保存玩家对象的指针,方便访问 public: - GameScene(const std::string& name, engine::core::Context& context, engine::scene::SceneManager& scene_manager); + GameScene(engine::core::Context& context, + engine::scene::SceneManager& scene_manager, + std::shared_ptr data = nullptr); // 覆盖场景基类的核心方法 void init() override; @@ -33,6 +40,7 @@ private: void handleObjectCollisions(); ///< @brief 处理游戏对象间的碰撞逻辑(从PhysicsEngine获取信息) void handleTileTriggers(); ///< @brief 处理瓦片触发事件(从PhysicsEngine获取信息) + void handlePlayerDamage(int damage); ///< @brief 处理玩家受伤(更新得分、UI等) void playerVSEnemyCollision(engine::object::GameObject* player, engine::object::GameObject* enemy); ///< @brief 玩家与敌人碰撞处理 void playerVSItemCollision(engine::object::GameObject* player, engine::object::GameObject* item); ///< @brief 玩家与道具碰撞处理 @@ -47,6 +55,9 @@ private: * @param tag 特效标签(决定特效类型,例如"enemy","item") */ void createEffect(const glm::vec2& center_pos, const std::string& tag); + + // 测试函数 + void testSaveAndLoad(); }; } // namespace game::scene \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index ce76ce5..9864134 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,7 @@ #include int main(int /* argc */, char* /* argv */[]) { - spdlog::set_level(spdlog::level::debug); + spdlog::set_level(spdlog::level::info); engine::core::GameApp app; app.run();