完成暂停与菜单场景

This commit is contained in:
Ziyu
2025-06-16 14:44:47 +08:00
parent 3225612819
commit ccc88a2aa1
13 changed files with 369 additions and 14 deletions
+2
View File
@@ -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
+2 -2
View File
@@ -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
}
+5 -3
View File
@@ -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
+5 -2
View File
@@ -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
+15 -1
View File
@@ -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<engine::core::GameState>(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;
+3
View File
@@ -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<engine::scene::SceneManager> scene_manager_;
std::unique_ptr<engine::physics::PhysicsEngine> physics_engine_;
std::unique_ptr<engine::audio::AudioPlayer> audio_player_;
std::unique_ptr<engine::core::GameState> 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();
};
+57
View File
@@ -0,0 +1,57 @@
#include "game_state.h"
#include <spdlog/spdlog.h>
#include <stdexcept>
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<int>(new_size.x), static_cast<int>(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<int>(new_size.x),
static_cast<int>(new_size.y),
SDL_LOGICAL_PRESENTATION_LETTERBOX);
spdlog::trace("逻辑分辨率设置为: {}x{}", new_size.x, new_size.y);
}
} // namespace engine::core
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <optional>
#include <string>
#include <glm/vec2.hpp>
#include <SDL3/SDL_video.h>
#include <SDL3/SDL_render.h>
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
+6 -4
View File
@@ -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();) {
+9 -1
View File
@@ -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<MenuScene>(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();
+149
View File
@@ -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 <spdlog/spdlog.h>
namespace game::scene {
MenuScene::MenuScene(engine::core::Context& context,
engine::scene::SceneManager& scene_manager,
std::shared_ptr<game::data::SessionData> 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<engine::ui::UILabel>(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<engine::ui::UIButton>(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<engine::ui::UIButton>(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<engine::ui::UIButton>(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<engine::ui::UIButton>(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<TitleScene>(context_, scene_manager_, session_data_));
}
void MenuScene::onQuitClicked() {
spdlog::debug("退出按钮被点击。请求应用程序退出。");
context_.getInputManager().setShouldQuit(true); // 输入管理器设置退出标志
}
} // namespace game::scene
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include "../../engine/scene/scene.h"
#include <memory>
// 前向声明
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<game::data::SessionData> 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<game::data::SessionData> 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
+3 -1
View File
@@ -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 失败!");