完成关卡切换-共享游戏数据

This commit is contained in:
Ziyu
2025-06-16 14:44:47 +08:00
parent e08b436e9a
commit a03a5cdf74
8 changed files with 231 additions and 12 deletions
+1
View File
@@ -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
)
# 链接库
+7
View File
@@ -0,0 +1,7 @@
{
"high_score": 20,
"level_health": 2,
"level_score": 20,
"map_path": "assets/maps/level2.tmj",
"max_health": 3
}
+1 -1
View File
@@ -61,7 +61,7 @@ bool GameApp::init() {
if (!initSceneManager()) return false;
// 创建第一个场景并压入栈
auto scene = std::make_unique<game::scene::GameScene>("level1", *context_, *scene_manager_);
auto scene = std::make_unique<game::scene::GameScene>(*context_, *scene_manager_);
scene_manager_->requestPushScene(std::move(scene));
is_running_ = true;
+104
View File
@@ -0,0 +1,104 @@
#include "session_data.h"
#include <fstream>
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <glm/common.hpp>
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
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include <string>
#include <memory>
#include <nlohmann/json.hpp>
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
+46 -9
View File
@@ -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 <spdlog/spdlog.h>
#include <SDL3/SDL_rect.h>
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<game::data::SessionData> data)
: Scene("GameScene", context, scene_manager), game_session_data_(std::move(data)) {
if (!game_session_data_) { // 如果没有传入SessionData,则创建一个默认的
game_session_data_ = std::make_shared<game::data::SessionData>();
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<game::component::PlayerComponent>()->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<game::component::PlayerComponent>();
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<engine::component::PhysicsComponent>()->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<game::component::PlayerComponent>()->takeDamage(1);
// TODO: 其他受伤逻辑
handlePlayerDamage(1);
}
}
@@ -276,7 +298,7 @@ void GameScene::playerVSItemCollision(engine::object::GameObject * player, engin
if (item->getName() == "fruit") {
player->getComponent<engine::component::HealthComponent>()->heal(1); // 加血
} else if (item->getName() == "gem") {
//TODO: 加分
game_session_data_->addScore(5);
}
item->setNeedRemove(true); // 标记道具为待删除状态
auto item_aabb = item->getComponent<engine::component::ColliderComponent>()->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<game::scene::GameScene>(scene_name, context_, scene_manager_);
auto map_path = levelNameToPath(scene_name);
game_session_data_->setNextLevel(map_path); // 设置下一个关卡信息
auto next_scene = std::make_unique<game::scene::GameScene>(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
+12 -1
View File
@@ -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::data::SessionData> 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<game::data::SessionData> 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
+1 -1
View File
@@ -2,7 +2,7 @@
#include <spdlog/spdlog.h>
int main(int /* argc */, char* /* argv */[]) {
spdlog::set_level(spdlog::level::debug);
spdlog::set_level(spdlog::level::info);
engine::core::GameApp app;
app.run();