From ccc88a2aa156396c826c90a376ed2eaeef8fe753 Mon Sep 17 00:00:00 2001 From: Ziyu Date: Sun, 15 Jun 2025 10:31:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9A=82=E5=81=9C=E4=B8=8E?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 + assets/save.json | 4 +- src/engine/core/context.cpp | 8 +- src/engine/core/context.h | 7 +- src/engine/core/game_app.cpp | 16 +++- src/engine/core/game_app.h | 3 + src/engine/core/game_state.cpp | 57 +++++++++++++ src/engine/core/game_state.h | 60 +++++++++++++ src/engine/scene/scene.cpp | 10 ++- src/game/scene/game_scene.cpp | 10 ++- src/game/scene/menu_scene.cpp | 149 +++++++++++++++++++++++++++++++++ src/game/scene/menu_scene.h | 53 ++++++++++++ src/game/scene/title_scene.cpp | 4 +- 13 files changed, 369 insertions(+), 14 deletions(-) create mode 100644 src/engine/core/game_state.cpp create mode 100644 src/engine/core/game_state.h create mode 100644 src/game/scene/menu_scene.cpp create mode 100644 src/game/scene/menu_scene.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 249460d..7104233 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ add_executable(${TARGET} src/engine/core/time.cpp src/engine/core/config.cpp src/engine/core/context.cpp + src/engine/core/game_state.cpp src/engine/resource/resource_manager.cpp src/engine/resource/texture_manager.cpp src/engine/resource/audio_manager.cpp @@ -73,6 +74,7 @@ add_executable(${TARGET} src/game/scene/game_scene.cpp src/game/scene/title_scene.cpp src/game/scene/helps_scene.cpp + src/game/scene/menu_scene.cpp src/game/component/player_component.cpp src/game/component/ai_component.cpp src/game/component/state/player_state.cpp diff --git a/assets/save.json b/assets/save.json index 8207e84..8c12115 100644 --- a/assets/save.json +++ b/assets/save.json @@ -1,7 +1,7 @@ { "high_score": 20, - "level_health": 2, - "level_score": 20, + "level_health": 1, + "level_score": 15, "map_path": "assets/maps/level2.tmj", "max_health": 3 } \ No newline at end of file diff --git a/src/engine/core/context.cpp b/src/engine/core/context.cpp index 12fcfc7..1ab3db7 100644 --- a/src/engine/core/context.cpp +++ b/src/engine/core/context.cpp @@ -16,16 +16,18 @@ Context::Context(engine::input::InputManager& input_manager, engine::render::TextRenderer& text_renderer, engine::resource::ResourceManager& resource_manager, engine::physics::PhysicsEngine& physics_engine, - engine::audio::AudioPlayer& audio_player) + engine::audio::AudioPlayer& audio_player, + engine::core::GameState& game_state) : input_manager_(input_manager), renderer_(renderer), camera_(camera), text_renderer_(text_renderer), resource_manager_(resource_manager), physics_engine_(physics_engine), - audio_player_(audio_player) + audio_player_(audio_player), + game_state_(game_state) { - spdlog::trace("上下文已创建并初始化,包含输入管理器、渲染器、相机和资源管理器。"); + spdlog::trace("上下文已创建并初始化。"); } } // namespace engine::core \ No newline at end of file diff --git a/src/engine/core/context.h b/src/engine/core/context.h index 31959a1..cc0f85d 100644 --- a/src/engine/core/context.h +++ b/src/engine/core/context.h @@ -23,6 +23,7 @@ namespace engine::audio { } namespace engine::core { + class GameState; /** * @brief 持有对核心引擎模块引用的上下文对象。 @@ -39,7 +40,7 @@ private: engine::resource::ResourceManager& resource_manager_; ///< @brief 资源管理器 engine::physics::PhysicsEngine& physics_engine_; ///< @brief 物理引擎 engine::audio::AudioPlayer& audio_player_; ///< @brief 音频播放器 - + engine::core::GameState& game_state_; ///< @brief 游戏状态 public: /** * @brief 构造函数。 @@ -55,7 +56,8 @@ public: engine::render::TextRenderer& text_renderer, engine::resource::ResourceManager& resource_manager, engine::physics::PhysicsEngine& physics_engine, - engine::audio::AudioPlayer& audio_player); + engine::audio::AudioPlayer& audio_player, + engine::core::GameState& game_state); // 禁止拷贝和移动,Context 对象通常是唯一的或按需创建/传递 Context(const Context&) = delete; @@ -71,6 +73,7 @@ public: engine::resource::ResourceManager& getResourceManager() const { return resource_manager_; } ///< @brief 获取资源管理器 engine::physics::PhysicsEngine& getPhysicsEngine() const { return physics_engine_; } ///< @brief 获取物理引擎 engine::audio::AudioPlayer& getAudioPlayer() const { return audio_player_; } ///< @brief 获取音频播放器 + engine::core::GameState& getGameState() const { return game_state_; } ///< @brief 获取游戏状态 }; } // namespace engine::core \ No newline at end of file diff --git a/src/engine/core/game_app.cpp b/src/engine/core/game_app.cpp index f0ffe46..7b2553f 100644 --- a/src/engine/core/game_app.cpp +++ b/src/engine/core/game_app.cpp @@ -2,6 +2,7 @@ #include "time.h" #include "context.h" #include "config.h" +#include "game_state.h" #include "../resource/resource_manager.h" #include "../audio/audio_player.h" #include "../render/renderer.h" @@ -58,6 +59,7 @@ bool GameApp::init() { if (!initTextRenderer()) return false; if (!initInputManager()) return false; if (!initPhysicsEngine()) return false; + if (!initGameState()) return false; if (!initContext()) return false; if (!initSceneManager()) return false; @@ -256,6 +258,17 @@ bool GameApp::initPhysicsEngine() return true; } +bool GameApp::initGameState() +{ + try { + game_state_ = std::make_unique(window_, sdl_renderer_); + } catch (const std::exception& e) { + spdlog::error("初始化游戏状态失败: {}", e.what()); + return false; + } + return true; +} + bool GameApp::initContext() { try { @@ -265,7 +278,8 @@ bool GameApp::initContext() *text_renderer_, *resource_manager_, *physics_engine_, - *audio_player_); + *audio_player_, + *game_state_); } catch (const std::exception& e) { spdlog::error("初始化上下文失败: {}", e.what()); return false; diff --git a/src/engine/core/game_app.h b/src/engine/core/game_app.h index 8b3c09d..11516e5 100644 --- a/src/engine/core/game_app.h +++ b/src/engine/core/game_app.h @@ -35,6 +35,7 @@ namespace engine::core { // 命名空间的最佳实践:与文件路径 class Time; class Config; class Context; +class GameState; /** * @brief 主游戏应用程序类,初始化SDL,管理游戏循环。 @@ -57,6 +58,7 @@ private: std::unique_ptr scene_manager_; std::unique_ptr physics_engine_; std::unique_ptr audio_player_; + std::unique_ptr game_state_; public: GameApp(); @@ -91,6 +93,7 @@ private: [[nodiscard]] bool initCamera(); [[nodiscard]] bool initInputManager(); [[nodiscard]] bool initPhysicsEngine(); + [[nodiscard]] bool initGameState(); [[nodiscard]] bool initContext(); [[nodiscard]] bool initSceneManager(); }; diff --git a/src/engine/core/game_state.cpp b/src/engine/core/game_state.cpp new file mode 100644 index 0000000..dd66618 --- /dev/null +++ b/src/engine/core/game_state.cpp @@ -0,0 +1,57 @@ +#include "game_state.h" +#include +#include + +namespace engine::core { + +GameState::GameState(SDL_Window* window, SDL_Renderer* renderer, State initial_state) + : window_(window), renderer_(renderer), current_state_(initial_state){ + if (window_ == nullptr || renderer_ == nullptr) { + spdlog::error("窗口或渲染器为空"); + throw std::runtime_error("窗口或渲染器不能为空"); + } + spdlog::trace("游戏状态初始化完成"); +} + +void GameState::setState(State new_state) { + if (current_state_ != new_state) { + spdlog::debug("游戏状态改变"); + current_state_ = new_state; + } else { + spdlog::debug("尝试设置相同的游戏状态,跳过"); + } +} + +glm::vec2 GameState::getWindowSize() const +{ + int width, height; + // SDL3获取窗口大小的方法 + SDL_GetWindowSize(window_, &width, &height); + return glm::vec2(width, height); +} + +void GameState::setWindowSize(glm::vec2 new_size) +{ + SDL_SetWindowSize(window_, static_cast(new_size.x), static_cast(new_size.y)); +} + +glm::vec2 GameState::getLogicalSize() const +{ + int width, height; + // SDL3获取逻辑分辨率的方法 + SDL_GetRenderLogicalPresentation(renderer_, &width, &height, NULL); + return glm::vec2(width, height); +} + + +void GameState::setLogicalSize(glm::vec2 new_size) +{ + SDL_SetRenderLogicalPresentation(renderer_, + static_cast(new_size.x), + static_cast(new_size.y), + SDL_LOGICAL_PRESENTATION_LETTERBOX); + spdlog::trace("逻辑分辨率设置为: {}x{}", new_size.x, new_size.y); +} + + +} // namespace engine::core \ No newline at end of file diff --git a/src/engine/core/game_state.h b/src/engine/core/game_state.h new file mode 100644 index 0000000..b02fffd --- /dev/null +++ b/src/engine/core/game_state.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace engine::core { + +/** + * @enum State + * @brief 定义游戏可能处于的宏观状态。 + */ +enum class State { + Title, ///< @brief 标题界面 + Playing, ///< @brief 正常游戏进行中 + Paused, ///< @brief 游戏暂停(通常覆盖菜单界面) + GameOver, ///< @brief 游戏结束界面 + // 可以根据需要添加更多状态,如 Cutscene, SettingsMenu 等 +}; + +/** + * @brief 管理和查询游戏的全局宏观状态。 + * + * 提供一个中心点来确定游戏当前处于哪个主要模式, + * 以便其他系统(输入、渲染、更新等)可以相应地调整其行为。 + */ +class GameState final { +private: + SDL_Window* window_ = nullptr; ///< @brief SDL窗口,用于获取窗口大小 + SDL_Renderer* renderer_ = nullptr; ///< @brief SDL渲染器,用于获取逻辑分辨率 + State current_state_ = State::Title; ///< @brief 当前游戏状态 + +public: + /** + * @brief 构造函数,初始化游戏状态。 + * @param window SDL窗口,必须传入有效值。 + * @param renderer SDL渲染器,必须传入有效值。 + * @param initial_state 游戏的初始状态,默认为 Title + */ + explicit GameState(SDL_Window* window, SDL_Renderer* renderer, State initial_state = State::Title); + + State getCurrentState() const { return current_state_; } + void setState(State new_state); + glm::vec2 getWindowSize() const; + void setWindowSize(glm::vec2 new_size); + glm::vec2 getLogicalSize() const; + void setLogicalSize(glm::vec2 new_size); + + + // --- 便捷查询方法 --- + + bool isInTitle() const { return current_state_ == State::Title; } + bool isPlaying() const { return current_state_ == State::Playing; } + bool isPaused() const { return current_state_ == State::Paused; } + bool isGameOver() const { return current_state_ == State::GameOver; } + +}; + +} // namespace engine::core \ No newline at end of file diff --git a/src/engine/scene/scene.cpp b/src/engine/scene/scene.cpp index 13e269e..8bd5ace 100644 --- a/src/engine/scene/scene.cpp +++ b/src/engine/scene/scene.cpp @@ -2,6 +2,7 @@ #include "scene_manager.h" #include "../object/game_object.h" #include "../core/context.h" +#include "../core/game_state.h" #include "../physics/physics_engine.h" #include "../render/camera.h" #include "../ui/ui_manager.h" @@ -29,10 +30,11 @@ void Scene::init() { void Scene::update(float delta_time) { if (!is_initialized_) return; - // 先更新物理引擎 - context_.getPhysicsEngine().update(delta_time); - // 更新相机 - context_.getCamera().update(delta_time); + // 只有游戏进行中,才需要更新物理引擎和相机 + if (context_.getGameState().isPlaying()){ + context_.getPhysicsEngine().update(delta_time); + context_.getCamera().update(delta_time); + } // 更新所有游戏对象,并删除需要移除的对象 for (auto it = game_objects_.begin(); it != game_objects_.end();) { diff --git a/src/game/scene/game_scene.cpp b/src/game/scene/game_scene.cpp index 9d1fb5b..c01a21f 100644 --- a/src/game/scene/game_scene.cpp +++ b/src/game/scene/game_scene.cpp @@ -1,6 +1,8 @@ #include "game_scene.h" +#include "menu_scene.h" #include "../component/player_component.h" #include "../../engine/core/context.h" +#include "../../engine/core/game_state.h" #include "../../engine/object/game_object.h" #include "../../engine/component/transform_component.h" #include "../../engine/component/sprite_component.h" @@ -49,6 +51,7 @@ void GameScene::init() { return; } spdlog::trace("GameScene 初始化开始..."); + context_.getGameState().setState(engine::core::State::Playing); if (!initLevel()) { spdlog::error("关卡初始化失败,无法继续。"); @@ -90,6 +93,11 @@ void GameScene::render() { void GameScene::handleInput() { Scene::handleInput(); + // 检查暂停动作 + if (context_.getInputManager().isActionPressed("pause")) { + spdlog::debug("在GameScene中检测到暂停动作,正在推送MenuScene。"); + scene_manager_.requestPushScene(std::make_unique(context_, scene_manager_, game_session_data_)); + } } void GameScene::clean() { @@ -198,7 +206,7 @@ bool GameScene::initEnemyAndItem() bool GameScene::initUI() { - if (!ui_manager_->init(glm::vec2(640.0f, 360.0f))) return false; + if (!ui_manager_->init(context_.getGameState().getLogicalSize())) return false; createScoreUI(); createHealthUI(); diff --git a/src/game/scene/menu_scene.cpp b/src/game/scene/menu_scene.cpp new file mode 100644 index 0000000..18bad6c --- /dev/null +++ b/src/game/scene/menu_scene.cpp @@ -0,0 +1,149 @@ +#include "menu_scene.h" +#include "title_scene.h" +#include "../../engine/core/context.h" +#include "../../engine/core/game_state.h" +#include "../../engine/input/input_manager.h" +#include "../../engine/scene/scene_manager.h" +#include "../../engine/ui/ui_manager.h" +#include "../../engine/ui/ui_panel.h" +#include "../../engine/ui/ui_label.h" +#include "../../engine/ui/ui_button.h" +#include "../../engine/core/game_state.h" +#include "../../engine/audio/audio_player.h" +#include "../../engine/utils/math.h" +#include "../data/session_data.h" +#include + + +namespace game::scene { + +MenuScene::MenuScene(engine::core::Context& context, + engine::scene::SceneManager& scene_manager, + std::shared_ptr session_data) + : Scene("MenuScene", context, scene_manager), // 调用基类构造函数 + session_data_(std::move(session_data)) { + if (!session_data_) { + spdlog::error("菜单场景构造时 SessionData 为空。"); + } + spdlog::trace("MenuScene 构造完成."); +} + +void MenuScene::init() { + context_.getGameState().setState(engine::core::State::Paused); + createUI(); + + Scene::init(); + spdlog::trace("menuScene 初始化完成"); +} + +void MenuScene::createUI() { + + auto window_size = context_.getGameState().getLogicalSize(); + if (!ui_manager_->init(window_size)) { + spdlog::error("MenuScene 中初始化 UIManager 失败!"); + return; + } + + // "PAUSE"标签 + auto pause_label = std::make_unique(context_.getTextRenderer(), + "PAUSE", + "assets/fonts/VonwaonBitmap-16px.ttf", + 32); + // 放在中间靠上的位置 + auto size = pause_label->getSize(); + auto label_y = window_size.y * 0.2; + pause_label->setPosition(glm::vec2((window_size.x - size.x) / 2.0f, label_y)); + ui_manager_->addElement(std::move(pause_label)); + + // --- 创建按钮 --- (4个按钮,设定好大小、间距) + float button_width = 96.0f; // 按钮稍微小一点 + float button_height = 32.0f; + float button_spacing = 10.0f; + float start_y = label_y + 80.0f; // 从标签下方开始,增加间距 + float button_x = (window_size.x - button_width) / 2.0f; // 水平居中 + + // Resume Button + auto resume_button = std::make_unique(context_, + "assets/textures/UI/buttons/Resume1.png", + "assets/textures/UI/buttons/Resume2.png", + "assets/textures/UI/buttons/Resume3.png", + glm::vec2{button_x, start_y}, + glm::vec2{button_width, button_height}, + [this]() { this->onResumeClicked(); }); + ui_manager_->addElement(std::move(resume_button)); + + // Save Button + start_y += button_height + button_spacing; + auto save_button = std::make_unique(context_, + "assets/textures/UI/buttons/Save1.png", + "assets/textures/UI/buttons/Save2.png", + "assets/textures/UI/buttons/Save3.png", + glm::vec2{button_x, start_y}, + glm::vec2{button_width, button_height}, + [this]() { this->onSaveClicked(); }); + ui_manager_->addElement(std::move(save_button)); + + // Back Button + start_y += button_height + button_spacing; + auto back_button = std::make_unique(context_, + "assets/textures/UI/buttons/Back1.png", + "assets/textures/UI/buttons/Back2.png", + "assets/textures/UI/buttons/Back3.png", + glm::vec2{button_x, start_y}, + glm::vec2{button_width, button_height}, + [this]() { this->onBackClicked(); }); + ui_manager_->addElement(std::move(back_button)); + + // Quit Button + start_y += button_height + button_spacing; + auto quit_button = std::make_unique(context_, + "assets/textures/UI/buttons/Quit1.png", + "assets/textures/UI/buttons/Quit2.png", + "assets/textures/UI/buttons/Quit3.png", + glm::vec2{button_x, start_y}, + glm::vec2{button_width, button_height}, + [this]() { this->onQuitClicked(); }); + ui_manager_->addElement(std::move(quit_button)); +} + +void MenuScene::handleInput() { + // 先让 UIManager 处理交互 + Scene::handleInput(); + + // 检查暂停键,允许按暂停键恢复游戏 + if (context_.getInputManager().isActionPressed("pause")) { + spdlog::debug("在菜单场景中按下暂停键,正在恢复游戏..."); + scene_manager_.requestPopScene(); // 弹出自身以恢复底层的GameScene + context_.getGameState().setState(engine::core::State::Playing); + } +} + +// --- 按钮回调函数实现 --- + +void MenuScene::onResumeClicked() { + spdlog::debug("继续游戏按钮被点击。"); + scene_manager_.requestPopScene(); // 弹出当前场景 + context_.getGameState().setState(engine::core::State::Playing); +} + +void MenuScene::onSaveClicked() { + spdlog::debug("保存游戏按钮被点击。"); + if (session_data_ && session_data_->saveToFile("assets/save.json")) { + spdlog::debug("菜单场景中成功保存游戏数据。"); + } else { + spdlog::error("菜单场景中保存游戏数据失败。"); + } +} + +void MenuScene::onBackClicked() { + spdlog::debug("返回按钮被点击。弹出菜单场景和游戏场景,返回标题界面。"); + // 直接替换为TitleScene + scene_manager_.requestReplaceScene(std::make_unique(context_, scene_manager_, session_data_)); +} + +void MenuScene::onQuitClicked() { + spdlog::debug("退出按钮被点击。请求应用程序退出。"); + context_.getInputManager().setShouldQuit(true); // 输入管理器设置退出标志 +} + +} // namespace game::scene \ No newline at end of file diff --git a/src/game/scene/menu_scene.h b/src/game/scene/menu_scene.h new file mode 100644 index 0000000..e21de70 --- /dev/null +++ b/src/game/scene/menu_scene.h @@ -0,0 +1,53 @@ +#pragma once +#include "../../engine/scene/scene.h" +#include + +// 前向声明 +namespace engine::core { + class Context; +} +namespace engine::scene { + class SceneManager; +} +namespace game::data { + class SessionData; +} + +namespace game::scene { + +/** + * @brief 游戏暂停时显示的菜单场景,提供继续、保存、返回、退出等选项。 + * 该场景通常被推送到 GameScene 之上。 + */ +class MenuScene final : public engine::scene::Scene { +private: + std::shared_ptr session_data_; + +public: + /** + * @brief MenuScene 的构造函数 + * @param context 引擎上下文的引用 + * @param scene_manager 场景管理器的引用 + * @param session_data_ 场景间传递的游戏数据 + */ + MenuScene(engine::core::Context& context, + engine::scene::SceneManager& scene_manager, + std::shared_ptr session_data_); + + ~MenuScene() override = default; + + // --- 核心循环方法 --- + void init() override; + void handleInput() override; + +private: + // 用于创建UI和按钮回调的私有辅助方法 + void createUI(); + void onResumeClicked(); + void onSaveClicked(); + void onBackClicked(); + void onQuitClicked(); + +}; + +} // namespace game::scene \ No newline at end of file diff --git a/src/game/scene/title_scene.cpp b/src/game/scene/title_scene.cpp index c5ccc61..ab47144 100644 --- a/src/game/scene/title_scene.cpp +++ b/src/game/scene/title_scene.cpp @@ -1,5 +1,6 @@ #include "title_scene.h" #include "../../engine/core/context.h" +#include "../../engine/core/game_state.h" #include "../../engine/resource/resource_manager.h" #include "../../engine/render/camera.h" #include "../../engine/input/input_manager.h" @@ -55,7 +56,8 @@ void TitleScene::init() { // 创建 UI 界面元素 void TitleScene::createUI() { spdlog::trace("创建 TitleScene UI..."); - auto window_size = glm::vec2(640.0f, 360.0f); + context_.getGameState().setState(engine::core::State::Title); + auto window_size = context_.getGameState().getLogicalSize(); if (!ui_manager_->init(window_size)) { spdlog::error("初始化 UIManager 失败!");