完成场景与场景管理

This commit is contained in:
Ziyu
2025-06-16 14:44:47 +08:00
parent 757b66afa3
commit 06c96b56fa
9 changed files with 570 additions and 89 deletions
+3
View File
@@ -45,6 +45,9 @@ add_executable(${TARGET}
src/engine/object/game_object.cpp
src/engine/component/sprite_component.cpp
src/engine/component/transform_component.cpp
src/engine/scene/scene.cpp
src/engine/scene/scene_manager.cpp
src/game/scene/game_scene.cpp
)
# 链接库
+18 -82
View File
@@ -5,17 +5,14 @@
#include "../render/renderer.h"
#include "../render/camera.h"
#include "../input/input_manager.h"
#include "../object/game_object.h"
#include "../component/transform_component.h"
#include "../component/sprite_component.h"
#include "config.h"
#include "../scene/scene_manager.h"
#include "../../game/scene/game_scene.h"
#include <SDL3/SDL.h>
#include <spdlog/spdlog.h>
namespace engine::core {
engine::object::GameObject game_object("test_game_object");
GameApp::GameApp() = default;
GameApp::~GameApp() {
@@ -57,13 +54,14 @@ bool GameApp::init() {
if (!initInputManager()) return false;
if (!initContext()) return false;
if (!initSceneManager()) return false;
// 测试资源管理器
testResourceManager();
// 创建第一个场景并压入栈
auto scene = std::make_unique<game::scene::GameScene>("GameScene", *context_, *scene_manager_);
scene_manager_->requestPushScene(std::move(scene));
is_running_ = true;
spdlog::trace("GameApp 初始化成功。");
testGameObject();
return true;
}
@@ -74,12 +72,12 @@ void GameApp::handleEvents() {
return;
}
testInputManager();
scene_manager_->handleInput();
}
void GameApp::update(float /* delta_time */) {
void GameApp::update(float delta_time) {
// 游戏逻辑更新
testCamera();
scene_manager_->update(delta_time);
}
void GameApp::render() {
@@ -87,8 +85,7 @@ void GameApp::render() {
renderer_->clearScreen();
// 2. 具体渲染代码
testRenderer();
game_object.render(*context_);
scene_manager_->render();
// 3. 更新屏幕显示
renderer_->present();
@@ -222,77 +219,16 @@ bool GameApp::initContext()
return true;
}
// --- 测试用函数 ---
void GameApp::testResourceManager()
bool GameApp::initSceneManager()
{
resource_manager_->getTexture("assets/textures/Actors/eagle-attack.png");
resource_manager_->getFont("assets/fonts/VonwaonBitmap-16px.ttf", 16);
resource_manager_->getSound("assets/audio/button_click.wav");
resource_manager_->unloadTexture("assets/textures/Actors/eagle-attack.png");
resource_manager_->unloadFont("assets/fonts/VonwaonBitmap-16px.ttf", 16);
resource_manager_->unloadSound("assets/audio/button_click.wav");
}
void GameApp::testRenderer()
{
engine::render::Sprite sprite_world("assets/textures/Actors/frog.png");
engine::render::Sprite sprite_ui("assets/textures/UI/buttons/Start1.png");
engine::render::Sprite sprite_parallax("assets/textures/Layers/back.png");
static float rotation = 0.0f;
rotation += 0.1f;
// 注意渲染顺序
renderer_->drawParallax(*camera_, sprite_parallax, glm::vec2(100, 100), glm::vec2(0.5f, 0.5f), glm::bvec2(true, false));
renderer_->drawSprite(*camera_, sprite_world, glm::vec2(200, 200), glm::vec2(1.0f, 1.0f), rotation);
renderer_->drawUISprite(sprite_ui, glm::vec2(100, 100));
}
void GameApp::testCamera()
{
auto key_state = SDL_GetKeyboardState(nullptr);
if (key_state[SDL_SCANCODE_UP]) camera_->move(glm::vec2(0, -1));
if (key_state[SDL_SCANCODE_DOWN]) camera_->move(glm::vec2(0, 1));
if (key_state[SDL_SCANCODE_LEFT]) camera_->move(glm::vec2(-1, 0));
if (key_state[SDL_SCANCODE_RIGHT]) camera_->move(glm::vec2(1, 0));
}
void GameApp::testInputManager()
{
std::vector<std::string> actions = {
"move_up",
"move_down",
"move_left",
"move_right",
"jump",
"attack",
"pause",
"MouseLeftClick",
"MouseRightClick"
};
for (const auto& action : actions) {
if (input_manager_->isActionPressed(action)) {
spdlog::info(" {} 按下 ", action);
}
if (input_manager_->isActionReleased(action)) {
spdlog::info(" {} 抬起 ", action);
}
if (input_manager_->isActionDown(action)) {
spdlog::info(" {} 按下中 ", action);
}
try {
scene_manager_ = std::make_unique<engine::scene::SceneManager>(*context_);
} catch (const std::exception& e) {
spdlog::error("初始化场景管理器失败: {}", e.what());
return false;
}
}
void GameApp::testGameObject()
{
game_object.addComponent<engine::component::TransformComponent>(glm::vec2(100, 100));
game_object.addComponent<engine::component::SpriteComponent>("assets/textures/Props/big-crate.png", *resource_manager_, engine::utils::Alignment::CENTER);
game_object.getComponent<engine::component::TransformComponent>()->setScale(glm::vec2(2.0f, 2.0f));
game_object.getComponent<engine::component::TransformComponent>()->setRotation(30.0f);
spdlog::trace("场景管理器初始化成功。");
return true;
}
} // namespace engine::core
+6 -7
View File
@@ -18,6 +18,10 @@ namespace engine::input {
class InputManager;
}
namespace engine::scene {
class SceneManager;
}
namespace engine::core { // 命名空间的最佳实践:与文件路径一致
class Time;
class Config;
@@ -40,6 +44,7 @@ private:
std::unique_ptr<engine::core::Config> config_;
std::unique_ptr<engine::input::InputManager> input_manager_;
std::unique_ptr<engine::core::Context> context_;
std::unique_ptr<engine::scene::SceneManager> scene_manager_;
public:
GameApp();
@@ -72,13 +77,7 @@ private:
[[nodiscard]] bool initCamera();
[[nodiscard]] bool initInputManager();
[[nodiscard]] bool initContext();
// 测试用函数
void testResourceManager();
void testRenderer();
void testCamera();
void testInputManager();
void testGameObject();
[[nodiscard]] bool initSceneManager();
};
} // namespace engine::core
+134
View File
@@ -0,0 +1,134 @@
#include "scene.h"
#include "../object/game_object.h"
#include "scene_manager.h"
#include <algorithm> // for std::remove_if
#include <spdlog/spdlog.h>
namespace engine::scene {
Scene::Scene(std::string name, engine::core::Context& context, engine::scene::SceneManager& scene_manager)
: scene_name_(std::move(name)), context_(context), scene_manager_(scene_manager), is_initialized_(false) {
spdlog::trace("场景 '{}' 构造完成。", scene_name_);
}
Scene::~Scene() = default;
void Scene::init() {
is_initialized_ = true; // 子类应该最后调用父类的 init 方法
spdlog::trace("场景 '{}' 初始化完成。", scene_name_);
}
void Scene::update(float delta_time) {
if (!is_initialized_) return;
// 更新所有游戏对象,并删除需要移除的对象
for (auto it = game_objects_.begin(); it != game_objects_.end();) {
if (*it && !(*it)->isNeedRemove()) { // 正常更新游戏对象
(*it)->update(delta_time, context_);
++it;
} else {
if (*it) { // 安全删除需要移除的对象
(*it)->clean();
}
it = game_objects_.erase(it); // 删除需要移除的对象,智能指针自动管理内存
}
}
processPendingAdditions(); // 处理待添加(延时添加)的游戏对象
}
void Scene::render() {
if (!is_initialized_) return;
// 渲染所有游戏对象
for (const auto& obj : game_objects_) {
if (obj) obj->render(context_);
}
}
void Scene::handleInput() {
if (!is_initialized_) return;
// 遍历所有游戏对象,并删除需要移除的对象
for (auto it = game_objects_.begin(); it != game_objects_.end();) {
if (*it && !(*it)->isNeedRemove()) {
(*it)->handleInput(context_);
++it;
} else {
// 安全删除需要移除的对象
if (*it) (*it)->clean();
it = game_objects_.erase(it);
}
}
}
void Scene::clean() {
if (!is_initialized_) return;
for (const auto& obj : game_objects_) {
if (obj) obj->clean();
}
game_objects_.clear();
is_initialized_ = false; // 清理完成后,设置场景为未初始化
spdlog::trace("场景 '{}' 清理完成。", scene_name_);
}
void Scene::addGameObject(std::unique_ptr<engine::object::GameObject>&& game_object) {
if (game_object) game_objects_.push_back(std::move(game_object));
else spdlog::warn("尝试向场景 '{}' 添加空游戏对象。", scene_name_);
}
void Scene::safeAddGameObject(std::unique_ptr<engine::object::GameObject>&& game_object)
{
if (game_object) pending_additions_.push_back(std::move(game_object));
else spdlog::warn("尝试向场景 '{}' 添加空游戏对象。", scene_name_);
}
void Scene::removeGameObject(engine::object::GameObject* game_object_ptr) {
if (!game_object_ptr) {
spdlog::warn("尝试从场景 '{}' 中移除一个空的游戏对象指针。", scene_name_);
return;
}
// erase-remove 移除法不可用,因为智能指针与裸指针无法比较
// 需要使用 std::remove_if 和 lambda 表达式自定义比较的方式
auto it = std::remove_if(game_objects_.begin(), game_objects_.end(),
[game_object_ptr](const std::unique_ptr<engine::object::GameObject>& p) {
return p.get() == game_object_ptr; // 比较裸指针是否相等(自定义比较方式)
});
if (it != game_objects_.end()) {
(*it)->clean(); // 因为传入的是指针,因此只可能有一个元素被移除,不需要遍历it到末尾
game_objects_.erase(it, game_objects_.end()); // 删除从it到末尾的元素(最后一个元素)
spdlog::trace("从场景 '{}' 中移除游戏对象。", scene_name_);
} else {
spdlog::warn("游戏对象指针未找到在场景 '{}' 中。", scene_name_);
}
}
void Scene::safeRemoveGameObject(engine::object::GameObject* game_object_ptr)
{
game_object_ptr->setNeedRemove(true);
}
engine::object::GameObject *Scene::findGameObjectByName(const std::string &name) const
{
// 找到第一个符合条件的游戏对象就返回
for (const auto& obj : game_objects_) {
if (obj && obj->getName() == name) {
return obj.get();
}
}
return nullptr;
}
void Scene::processPendingAdditions()
{
// 处理待添加的游戏对象
for (auto& game_object : pending_additions_) {
addGameObject(std::move(game_object));
}
pending_additions_.clear();
}
} // namespace engine::scene
+99
View File
@@ -0,0 +1,99 @@
#pragma once
#include <vector>
#include <memory>
#include <string>
namespace engine::core {
class Context;
}
namespace engine::render {
class Renderer;
class Camera;
}
namespace engine::input {
class InputManager;
}
namespace engine::object {
class GameObject;
}
namespace engine::scene {
class SceneManager;
/**
* @brief 场景基类,负责管理场景中的游戏对象和场景生命周期。
*
* 包含一组游戏对象,并提供更新、渲染、处理输入和清理的接口。
* 派生类应实现具体的场景逻辑。
*/
class Scene {
protected:
std::string scene_name_; ///< @brief 场景名称
engine::core::Context& context_; ///< @brief 上下文引用(隐式,构造时传入)
engine::scene::SceneManager& scene_manager_; ///< @brief 场景管理器引用(构造时传入)
bool is_initialized_ = false; ///< @brief 场景是否已初始化(非当前场景很可能未被删除,因此需要初始化标志避免重复初始化)
std::vector<std::unique_ptr<engine::object::GameObject>> game_objects_; ///< @brief 场景中的游戏对象
std::vector<std::unique_ptr<engine::object::GameObject>> pending_additions_; ///< @brief 待添加的游戏对象(延时添加)
public:
/**
* @brief 构造函数。
*
* @param name 场景的名称。
* @param context 场景上下文。
* @param scene_manager 场景管理器。
*/
Scene(std::string name, engine::core::Context& context, engine::scene::SceneManager& scene_manager);
virtual ~Scene(); // 1. 基类必须声明虚析构函数才能让派生类析构函数被正确调用。
// 2. 析构函数定义必须写在cpp中,不然需要引入GameObject头文件
// 禁止拷贝和移动构造
Scene(const Scene&) = delete;
Scene& operator=(const Scene&) = delete;
Scene(Scene&&) = delete;
Scene& operator=(Scene&&) = delete;
// 核心循环方法
virtual void init(); ///< @brief 初始化场景。
virtual void update(float delta_time); ///< @brief 更新场景。
virtual void render(); ///< @brief 渲染场景。
virtual void handleInput(); ///< @brief 处理输入。
virtual void clean(); ///< @brief 清理场景。
/// @brief 直接向场景中添加一个游戏对象。(初始化时可用,游戏进行中不安全) (&&表示右值引用,与std::move搭配使用,避免拷贝)
virtual void addGameObject(std::unique_ptr<engine::object::GameObject>&& game_object);
/// @brief 安全地添加游戏对象。(添加到pending_additions_中)
virtual void safeAddGameObject(std::unique_ptr<engine::object::GameObject>&& game_object);
/// @brief 直接从场景中移除一个游戏对象。(一般不使用,但保留实现的逻辑)
virtual void removeGameObject(engine::object::GameObject* game_object_ptr);
/// @brief 安全地移除游戏对象。(设置need_remove_标记)
virtual void safeRemoveGameObject(engine::object::GameObject* game_object_ptr);
/// @brief 获取场景中的游戏对象容器。
const std::vector<std::unique_ptr<engine::object::GameObject>>& getGameObjects() const { return game_objects_; }
/// @brief 根据名称查找游戏对象(返回找到的第一个对象)。
engine::object::GameObject* findGameObjectByName(const std::string& name) const;
// getters and setters
void setName(const std::string& name) { scene_name_ = name; } ///< @brief 设置场景名称
const std::string& getName() const { return scene_name_; } ///< @brief 获取场景名称
void setInitialized(bool initialized) { is_initialized_ = initialized; } ///< @brief 设置场景是否已初始化
bool isInitialized() const { return is_initialized_; } ///< @brief 获取场景是否已初始化
engine::core::Context& getContext() const { return context_; } ///< @brief 获取上下文引用
engine::scene::SceneManager& getSceneManager() const { return scene_manager_; } ///< @brief 获取场景管理器引用
std::vector<std::unique_ptr<engine::object::GameObject>>& getGameObjects() { return game_objects_; } ///< @brief 获取场景中的游戏对象
protected:
void processPendingAdditions(); ///< @brief 处理待添加的游戏对象。(每轮更新的最后调用)
};
} // namespace engine::scene
+160
View File
@@ -0,0 +1,160 @@
#include "scene_manager.h"
#include "scene.h"
#include "../core/context.h"
#include <spdlog/spdlog.h>
namespace engine::scene {
SceneManager::SceneManager(engine::core::Context& context)
: context_(context) {
spdlog::trace("场景管理器已创建。");
}
SceneManager::~SceneManager() {
spdlog::trace("场景管理器已销毁。");
close(); // 即使不手动调用 close 也能确保清理
}
Scene* SceneManager::getCurrentScene() const {
if (scene_stack_.empty()) {
return nullptr;
}
return scene_stack_.back().get(); // 返回栈顶场景的裸指针
}
void SceneManager::update(float delta_time) {
// 只更新栈顶(当前)场景
Scene* current_scene = getCurrentScene();
if (current_scene) {
current_scene->update(delta_time);
}
// 执行可能的切换场景操作
processPendingActions();
}
void SceneManager::render() {
// 渲染时需要叠加渲染所有场景,而不只是栈顶
for (const auto& scene : scene_stack_) {
if (scene) {
scene->render();
}
}
}
void SceneManager::handleInput() {
// 只考虑栈顶场景
Scene* current_scene = getCurrentScene();
if (current_scene) {
current_scene->handleInput();
}
}
void SceneManager::close() {
spdlog::trace("正在关闭场景管理器并清理场景栈...");
// 清理栈中所有剩余的场景(从顶到底)
while (!scene_stack_.empty()) {
if (scene_stack_.back()) {
spdlog::debug("正在清理场景 '{}' 。", scene_stack_.back()->getName());
scene_stack_.back()->clean();
}
scene_stack_.pop_back();
}
}
void SceneManager::requestPopScene()
{
pending_action_ = PendingAction::Pop;
}
void SceneManager::requestReplaceScene(std::unique_ptr<Scene>&& scene)
{
pending_action_ = PendingAction::Replace;
pending_scene_ = std::move(scene);
}
void SceneManager::requestPushScene(std::unique_ptr<Scene>&& scene)
{
pending_action_ = PendingAction::Push;
pending_scene_ = std::move(scene);
}
// --- Private Methods ---
void SceneManager::processPendingActions()
{
if (pending_action_ == PendingAction::None) {
return;
}
switch (pending_action_) {
case PendingAction::Pop:
popScene();
break;
case PendingAction::Replace:
replaceScene(std::move(pending_scene_));
break;
case PendingAction::Push:
pushScene(std::move(pending_scene_));
break;
default:
break;
}
pending_action_ = PendingAction::None;
}
void SceneManager::pushScene(std::unique_ptr<Scene>&& scene) {
if (!scene) {
spdlog::warn("尝试将空场景压入栈。");
return;
}
spdlog::debug("正在将场景 '{}' 压入栈。", scene->getName());
// 初始化新场景
if (!scene->isInitialized()) { // 确保只初始化一次
scene->init();
}
// 将新场景移入栈顶
scene_stack_.push_back(std::move(scene));
}
void SceneManager::popScene() {
if (scene_stack_.empty()) {
spdlog::warn("尝试从空场景栈中弹出。");
return;
}
spdlog::debug("正在从栈中弹出场景 '{}' 。", scene_stack_.back()->getName());
// 清理并移除栈顶场景
if (scene_stack_.back()) {
scene_stack_.back()->clean(); // 显式调用清理
}
scene_stack_.pop_back();
}
void SceneManager::replaceScene(std::unique_ptr<Scene>&& scene) {
if (!scene) {
spdlog::warn("尝试用空场景替换。");
return;
}
spdlog::debug("正在用场景 '{}' 替换场景 '{}' 。", scene->getName(), scene_stack_.back()->getName());
// 清理并移除场景栈中所有场景
while (!scene_stack_.empty()) {
if (scene_stack_.back()) {
scene_stack_.back()->clean();
}
scene_stack_.pop_back();
}
// 初始化新场景
if (!scene->isInitialized()) {
scene->init();
}
// 将新场景压入栈顶
scene_stack_.push_back(std::move(scene));
}
} // namespace engine::scene
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
// 前置声明
namespace engine::core {
class Context;
}
namespace engine::scene {
class Scene;
}
namespace engine::scene {
/**
* @brief 管理游戏中的场景栈,处理场景切换和生命周期。
*/
class SceneManager final {
private:
engine::core::Context& context_; ///< @brief 引擎上下文引用
std::vector<std::unique_ptr<Scene>> scene_stack_; ///< @brief 场景栈
enum class PendingAction { None, Push, Pop, Replace }; ///< @brief 待处理的动作
PendingAction pending_action_ = PendingAction::None; ///< @brief 待处理的动作
std::unique_ptr<Scene> pending_scene_; ///< @brief 待处理场景
public:
explicit SceneManager(engine::core::Context& context);
~SceneManager();
// 禁止拷贝和移动
SceneManager(const SceneManager&) = delete;
SceneManager& operator=(const SceneManager&) = delete;
SceneManager(SceneManager&&) = delete;
SceneManager& operator=(SceneManager&&) = delete;
// 延时切换场景
void requestPushScene(std::unique_ptr<Scene>&& scene); ///< @brief 请求压入一个新场景。
void requestPopScene(); ///< @brief 请求弹出当前场景。
void requestReplaceScene(std::unique_ptr<Scene>&& scene); ///< @brief 请求替换当前场景。
// getters
Scene* getCurrentScene() const; ///< @brief 获取当前活动场景(栈顶场景)的指针。
engine::core::Context& getContext() const { return context_; } ///< @brief 获取引擎上下文引用。
// 核心循环函数
void update(float delta_time);
void render();
void handleInput();
void close();
private:
void processPendingActions(); ///< @brief 处理挂起的场景操作(每轮更新最后调用)。
// 直接切换场景
void pushScene(std::unique_ptr<Scene>&& scene); ///< @brief 将一个新场景压入栈顶,使其成为活动场景。
void popScene(); ///< @brief 移除栈顶场景。
void replaceScene(std::unique_ptr<Scene>&& scene); ///< @brief 清理场景栈所有场景,将此场景设为栈顶场景。
};
} // namespace engine::scene
+56
View File
@@ -0,0 +1,56 @@
#include "game_scene.h"
#include "../../engine/core/context.h"
#include "../../engine/object/game_object.h"
#include "../../engine/component/transform_component.h"
#include "../../engine/component/sprite_component.h"
#include <spdlog/spdlog.h>
#include <SDL3/SDL_rect.h>
namespace game::scene {
// 构造函数:调用基类构造函数
GameScene::GameScene(std::string name, engine::core::Context& context, engine::scene::SceneManager& scene_manager)
: Scene(name, context, scene_manager) {
spdlog::trace("GameScene 构造完成。");
}
void GameScene::init() {
// 创建 test_object
createTestObject();
Scene::init();
spdlog::trace("GameScene 初始化完成。");
}
void GameScene::update(float delta_time) {
Scene::update(delta_time);
}
void GameScene::render() {
Scene::render();
}
void GameScene::handleInput() {
Scene::handleInput();
}
void GameScene::clean() {
Scene::clean();
}
// --- 私有方法 ---
void GameScene::createTestObject() {
spdlog::trace("在 GameScene 中创建 test_object...");
auto test_object = std::make_unique<engine::object::GameObject>("test_object");
// 添加组件
test_object->addComponent<engine::component::TransformComponent>(glm::vec2(100.0f, 100.0f));
test_object->addComponent<engine::component::SpriteComponent>("assets/textures/Props/big-crate.png", context_.getResourceManager());
// 将创建好的 GameObject 添加到场景中 (一定要用std::move,否则传递的是左值)
addGameObject(std::move(test_object));
spdlog::trace("test_object 创建并添加到 GameScene 中。");
}
} // namespace game::scene
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "../../engine/scene/scene.h"
#include <memory>
// 前置声明
namespace engine::object {
class GameObject;
}
namespace game::scene {
/**
* @brief 主要的游戏场景,包含玩家、敌人、关卡元素等。
*/
class GameScene final: public engine::scene::Scene {
public:
GameScene(std::string name, engine::core::Context& context, engine::scene::SceneManager& scene_manager);
// 覆盖场景基类的核心方法
void init() override;
void update(float delta_time) override;
void render() override;
void handleInput() override;
void clean() override;
private:
// 测试函数
void createTestObject();
};
} // namespace game::scene