完成关卡切换-共享游戏数据
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
# 链接库
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"high_score": 20,
|
||||
"level_health": 2,
|
||||
"level_score": 20,
|
||||
"map_path": "assets/maps/level2.tmj",
|
||||
"max_health": 3
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user