C++游戏开发之旅 8
问题概述
到目前为止,我们所有的测试都是直接写在GameApp中,这虽然很方便,但是随着项目的扩大,GameApp会变得很臃肿。
我们知道一个游戏包含多个状态:
|—— 主菜单
|—— 游戏关卡
|—— 第一关
|—— 第二关
|—— ...
|—— 暂停界面
|—— 设置界面
|—— 游戏结束界面
如果我们把所有逻辑都塞进一个类里,会是一个灾难。
这章里,我们将构建一个 场景管理系统,这实现了将游戏的不同部分划分到独立的 场景(Scene)中,并由 场景管理器(SceneManager)来统一调度。
这是构建一个真正可扩展游戏架构的关键一步。
1.核心概念:场景栈
简单介绍一下场景栈(Scene Stack)

场景栈,总是只能与 最上面的那个场景互动,先进后出,后进先出,可以想象成羽毛球筒,只能从开口处一个一个拿顶上的羽毛球。所以我们获得当前场景current_scene总是顶上的场景。
核心组件

GameApp中有个场景管理器SceneManager,场景管理器通过场景栈的方式管理场景Scene,场景中有很多游戏对象GameObject,每个游戏对象又有很多组件Component。
Scene(场景)
代表游戏的一个独立状态,比如MainMenuScene、GameScene、PauseScene。
特点:
- 每个场景都拥有和管理自己的一组游戏对象(GameObject)
- 场景之间相互独立
SceneManager(场景管理器)
负责维护一个场景"栈"的结构。
基本操作
| 栈操作 | 说明 | 使用场景 |
|---|---|---|
| Push(压入) | 将新场景压入栈顶 | 从主菜单进入游戏时,将GameScene压入栈顶 |
| Pop(弹出) | 将栈顶场景弹出 | 从游戏退出返回主菜单时,将GameScene弹出 |
| Release(替换) | 替换栈顶场景 | 从第一关切换到第二关时,用Level2Scene替换Level1Scene |
渲染与更新
| 生命周期方法 | 作用范围 | 说明 |
|---|---|---|
handleInput() |
仅栈顶场景 | 只有栈顶场景接收输入 |
update() |
仅栈顶场景 | 只有栈顶的场景更新逻辑 |
render() |
所有场景 | 渲染时可能会渲染栈中多个场景 |
比如植物大战僵尸,暂停的时候,菜单弹出,这个时候,栈顶的暂停菜单有个读报僵尸动画在播放的,而且有退出、设置的选项,对应事件处理、逻辑更新和渲染都是正常工作的,而底下的关卡场景像是停止了一样,但依然是在渲染。
2.项目搭建
现在我们需要在engine下创建scene目录,并在src下创建game目录,用于存放引擎无关的游戏逻辑代码。
|--- src/
|--- engine/ // 引擎层(可复用)
| |--- core/
| |--- resource/
| |--- render/
| |--- input/
| |--- object/
| |--- utils/
| |--- component/
| |--- scene/ // 场景系统
| |--- scene.h
| |--- scene.cpp
| |--- scene_manager.h
| |--- scene_manager.cpp
|--- game/ // 游戏层(项目)
|--- scene/
|--- game_scene.h
|--- game_scene.cpp
引擎层代码通用、可复用;游戏层代码是特定的。
3.Scene 基类
是所有具体场景的父类,定义了一个场景的基本框架。
scene.h
#pragma once
#include <vector>
#include <memory>
#include <string>
namespace engine::core{
class Context;
}
namespace engine::object{
class GameObject;
}
namespace engine::scene{
class SceneManager;
class Scene {
protected:
std::string scene_name_;
engine::core::Context& context_; // 引入context, update, render, handleInput可能需要使用到
engine::scene::SceneManager& sceneManager_;
bool is_initialized_ = false;
std::vector<std::unique_ptr<engine::object::GameObject>> game_objects_;
std::vector<std::unique_ptr<engine::object::GameObject>> pending_additions_; // 延时添加
public:
Scene(std::string scene_name, engine::core::Context& context, engine::scene::SceneManager& sceneManager);
virtual ~Scene(); // 默认析构函数 因为是基类,所以需要声明为虚函数,
// 而且由于智能指针前向声明原因,需要将其写在cpp中,不然就要引入gameobject的头文件了
// 禁用拷贝和移动语义
Scene(const Scene&) = delete;
Scene& operator=(const Scene&) = delete;
Scene(Scene&&) = delete;
Scene& operator=(Scene&&) = delete;
virtual void init(); // 初始化场景
virtual void update(float delta_time); // 更新场景
virtual void render(); // 渲染场景
virtual void handleInput(); // 处理输入
virtual void clean(); // 清理场景
// 直接向场景中添加一个游戏对象。(一般初始化时可用,游戏进行中不安全) (&&右值引用,与std::move搭配使用,避免拷贝)
virtual void addGameObject(std::unique_ptr<engine::object::GameObject>&& game_object);
// 安全地添加一个游戏对象。(添加到pending_additions_中)
virtual void safeAddGameObject(std::unique_ptr<engine::object::GameObject>&& game_object);
// 直接从场景中移除一个游戏对象。
virtual void removeGameObject(engine::object::GameObject* game_object_ptr);
// 安全地移除一个游戏对象。
virtual void safeRemoveGameObject(engine::object::GameObject* game_object_ptr);
// 获取场景中的游戏对象容器
const std::vector<std::unique_ptr<engine::object::GameObject>>& getGameObjects() const {
return game_objects_;
}
// 根据名称查找游戏对象(返回找到的第一个对象)
engine::object::GameObject* findGameObjectByName(const std::string& name) const;
// getter and setter
void setName(const std::string& name) { scene_name_ = name; }
const std::string& getName() const { return scene_name_; }
engine::core::Context& getContext() { return context_; }
engine::scene::SceneManager& getSceneManager() { return sceneManager_; }
private:
void processPendingAdditions(); // 处理待添加的游戏对象(每轮更新的最后调用)
};
}
核心职责
| 职责 | 说明 |
|---|---|
| 管理GameObject | 管理一个std::vector的GameObject,在自己的生命周期内调用这些对象的update,render,handleInput. |
| 安全的对象管理 | 提供safeAddGameObject和safeRemoveGameObject方法,避免在游戏循环中直接修改容器导致迭代器失效 |
安全的对象管理
在游戏循环中直接添加或删除对象(即修改game_objects_这个vector)是非常不安全的行为,因为这会使得迭代器失效,所以再遍历一个vector时我们是不会做这种添加或是删除的操作的。
方案
添加:safeAddGameObject() ---> 将对象放入 pending_additions_ 列表 ---> 在 update() 末尾调用 processPendingAdditions() ---> 安全的将对象加入到 game_objects_
删除:safeRemoveGameObject() ---> 给对象打上 need_remove_ 标记 ---> 在 update() 中遍历时检查标记 ---> 安全的移除标记的对象
scene.cpp
#include "scene.h"
#include "../object/game_object.h"
#include <spdlog/spdlog.h>
namespace engine::scene {
Scene::Scene(std::string scene_name, engine::core::Context &context, engine::scene::SceneManager &sceneManager)
: scene_name_(std::move(scene_name)), context_(context), sceneManager_(sceneManager)
{
is_initialized_ = false;
spdlog::info("创建场景{}", scene_name_);
}
Scene::~Scene() = default;
void Scene::init()
{
is_initialized_ = true;
spdlog::info("场景{} 已成功初始化", 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)->needRemove()){
(*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& game_object : game_objects_){
if(game_object) game_object->render(context_);
}
}
void Scene::handleInput()
{
if(!is_initialized_) return;
for (const auto& game_object : game_objects_){
if(game_object) game_object->handleInput(context_);
}
}
void Scene::clean()
{
if(!is_initialized_) return;
for (auto& game_object : game_objects_){
if(game_object) game_object->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>& game_object){return game_object_ptr == game_object.get();});
if(it != game_objects_.end()){
(*it)->clean(); // 清理对象 传入指针,只有一个对象被删除,不需要遍历it到末尾
game_objects_.erase(it);
spdlog::trace("场景{} 移除游戏对象{}", scene_name_, game_object_ptr->getName());
} 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& game_object : game_objects_){
if(game_object && game_object->getName() == name) return game_object.get();
}
return nullptr;
spdlog::warn("场景{} 中没有找到该游戏对象",scene_name_);
}
void Scene::processPendingAdditions()
{
for (auto& obj : pending_additions_){
if(obj) game_objects_.push_back(std::move(obj));
}
pending_additions_.clear();
}
}
4.SceneManager:场景的总调度
SceneManager是引擎的核心部分,负责场景的切换逻辑。
scene_manager.h
#pragma once
#include <vector>
#include <memory>
namespace engine::core {
class Context;
}
namespace engine::scene {
class Scene;
class SceneManager final {
private:
engine::core::Context& context_;
std::vector<std::unique_ptr<Scene>> scene_stack_; // 场景栈
enum class PendingAction {None, Push, Pop, Replace}; // 待执行的操作
PendingAction pending_action_ = PendingAction::None; // 待执行的操作
std::unique_ptr<Scene> pending_scene_; // 待执行的场景
public:
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); // 请求压入场景
void requestPopScene(); // 请求弹出场景
void requestReplaceScene(std::unique_ptr<Scene> scene); // 请求替换场景
// getter
Scene* getCurrentScene() const;
engine::core::Context& getContext() const { return context_; }
// 核心循环函数
void update(float delta_time);
void handleInput();
void render();
void close();
private:
void processPendingAction(); // 处理待执行的操作
void pushScene(std::unique_ptr<Scene> scene); // 压入场景
void popScene(); // 弹出场景
void replaceScene(std::unique_ptr<Scene> scene); // 替换场景
};
}
关键设计:延时处理(Pending Actions)
与Scene管理GameObject类似,SceneManager也不能在循环中去进行修改,而是在更新完后再进行处理。
原因:
- 避免在遍历栈时,修改栈结构
- 保证场景切换的稳定性和安全性
实现:
- 使用
pending_action_记录请求(Push、Pop、Replace) - 在
update的最后统一处理
请求场景切换 ---> 设置pending_action_ ---> 继续执行当前帧 ---> 帧末尾调用processPendingActions() ---> 安全地执行场景切换
scene_manager.cpp
#include "scene_manager.h"
#include "scene.h"
#include "spdlog/spdlog.h"
namespace engine::scene {
SceneManager::SceneManager(engine::core::Context &context)
: context_(context)
{
spdlog::trace("场景管理器创建成功");
}
SceneManager::~SceneManager() {
spdlog::trace("场景管理器销毁成功");
}
void SceneManager::requestPushScene(std::unique_ptr<Scene> scene)
{
pending_scene_ = std::move(scene);
pending_action_ = PendingAction::Push;
}
void SceneManager::requestPopScene()
{
if(!scene_stack_.empty()) {
pending_action_ = PendingAction::Pop;
}
}
void SceneManager::requestReplaceScene(std::unique_ptr<Scene> scene)
{
pending_scene_ = std::move(scene);
pending_action_ = PendingAction::Replace;
}
Scene *SceneManager::getCurrentScene() const
{ // 返回栈顶的场景
return scene_stack_.empty() ? nullptr : scene_stack_.back().get();
}
void SceneManager::update(float delta_time)
{
// 只更新栈顶的场景
Scene *current_scene = getCurrentScene();
if(current_scene) {
current_scene->update(delta_time);
}
processPendingAction();
}
void engine::scene::SceneManager::handleInput()
{
// 也是只处理栈顶的场景
Scene *current_scene = getCurrentScene();
if(current_scene) {
current_scene->handleInput();
}
}
void SceneManager::render()
{
for(const auto &scene : scene_stack_) {
if(scene) {
scene->render();
}
}
}
void SceneManager::close()
{
while(!scene_stack_.empty()) {
if(scene_stack_.back()) {
spdlog::debug("正在清理场景{}", scene_stack_.back()->getName());
scene_stack_.back()->clean();
}
scene_stack_.pop_back();
}
}
void SceneManager::processPendingAction()
{
if(pending_action_ != PendingAction::None) {
switch(pending_action_) {
case PendingAction::Push:
pushScene(std::move(pending_scene_));
break;
case PendingAction::Pop:
popScene();
break;
case PendingAction::Replace:
replaceScene(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());
// 关闭当前场景
close();
// 初始化新场景
if(!scene->isInitialized()) {
scene->init();
}
scene_stack_.push_back(std::move(scene));
}
}
5.GameScene:创建第一个具体场景
现在,修改一下之前在GameApp中的测试逻辑,迁移到专门的GameScene中,这是第一个属于game逻辑层的类。
src/game/scene/game_scene.h
#pragma once
#include "../../engine/scene/scene.h"
#include <memory>
namespace game::scene {
class GameScene final : public engine::scene::Scene {
public:
GameScene(std::string name, engine::core::Context& context, engine::scene::SceneManager& sceneManager);
// 核心方法
void init() override;
void update(float deltaTime) override;
void render() override;
void handleInput() override;
void clean() override;
private:
void createTestObject();
};
}
src/game/scene/game_scene.cpp
#include "game_scene.h"
#include <spdlog/spdlog.h>
#include "../../engine/core/context.h"
#include "../../engine/object/game_object.h"
#include "../../engine/component/transform_component.h"
#include "../../engine/component/sprite_component.h"
namespace game::scene{
GameScene::GameScene(std::string name, engine::core::Context &context, engine::scene::SceneManager &sceneManager)
: engine::scene::Scene(name, context, sceneManager){
spdlog::trace("GameScene 构造完成");
}
void GameScene::init()
{
// 添加测试对象
createTestObject();
// 完成后,调用父类的init
Scene::init();
}
void GameScene::update(float deltaTime)
{
Scene::update(deltaTime);
}
void GameScene::render()
{
Scene::render();
}
void GameScene::handleInput()
{
Scene::handleInput();
}
void GameScene::clean()
{
Scene::clean();
}
void GameScene::createTestObject()
{
std::unique_ptr<engine::object::GameObject> gameobj = std::make_unique<engine::object::GameObject>("test1");
// 添加组件进行测试
gameobj->addComponent<engine::component::TransformComponent>(glm::vec2(500.0f,100.0f));
gameobj->addComponent<engine::component::SpriteComponent>(
"assets/textures/Props/straw-house.png",
context_.getResourceManager(),
engine::utils::Alignment::CENTER
);
auto size = gameobj->getComponent<engine::component::SpriteComponent>()->getSpriteSize();
spdlog::info("Sprite size: {} {}",size.x,size.y);
// 修改TransformComponent大小
gameobj->getComponent<engine::component::TransformComponent>()->setScale(glm::vec2(0.5f,0.5f));
// 修改TransformComponent角度
gameobj->getComponent<engine::component::TransformComponent>()->setRotation(45.0f);
spdlog::info("Sprite size: {} {}",size.x,size.y);
addGameObject(std::move(gameobj));
}
} // namespace game::scene
设计说明
| 特性 | 说明 |
|---|---|
| 继承Scene基类 | 获得完整的场景生命周期管理 |
| 调用基类方法 | 在关键循环函数中调用Scene::xxx(),确保基类逻辑正确执行 |
| 逻辑封装 | 将对象创建封装到createTestObject()函数中 |
| 命名空间分离 | 使用game::scene命名空间,与引擎层分离 |
6.改造GameApp
修改GameApp,让器作为引擎的引导者和循环驱动者,不包含任何游戏逻辑。
game_app.h & game_app.cpp
// game_app.h
namespace engine::scene {
class SceneManager;
}
class GameApp final {
private:
std::unique_ptr<engine::scene::SceneManager> scene_manager_;
[[nodiscard]] bool initSceneManager();
};
// game_app.cpp
bool GameApp::init() {
// 初始化
if(!initSceneManager()) return false;
// 创建一个场景并压入栈
auto scene = std::make_unique<game::scene::GameScene>("GameScene",*context_,*sceneManager);
sceneManager->requestPushScene(std::move(scene));
is_running = true;
return true;
}
void GameApp::update(float delta_time)
{
scene_manager_->update(delta_time);
}
void GameApp::render()
{
renderer_->clearScreen();
scene_manager_->render();
renderer_->present();
}
bool GameApp::initSceneManager()
{
try {
scene_manager_ = std::make_unique<engine::scene::SceneManager>(*context_);
} catch (const std::exception& e){
spdlog::error("初始化SceneManager失败: {}", e.what());
return false;
}
return true;
}
移除 test... 后,GameApp的主循环就十分简洁
主循环(GameApp) ---> 委托给 SceneManager ---> SceneManager 调度当前场景 ---> Scene 管理游戏对象 ---> GameObject 执行组件逻辑
运行测试
可以正常显示

章节总结
这节实现了:
- 场景栈系统:支持Push,Pop,Replace三种场景切换操作
- 安全的生命周期管理:通过延时处理确保场景和对象的安全切换
- 分层架构:引擎层(engine)和游戏层(game)清晰分离
- 简化GameApp:将游戏逻辑从引擎核心层剥离
架构对比
| 特性 | 改造前 | 改造后 |
|---|---|---|
| 代码组织 | 所有逻辑都在GameApp中 |
按场景和功能模块划分 |
| 可拓展性 | 添加新状态困难 | 只需添加新场景类 |
| 代码复用 | 引擎和游戏逻辑混在一起 | 引擎层可在其他项目复用 |
| 维护性 | GameApp臃肿 |
每个场景独立维护 |
设计模式应用
| 模式 | 应用 |
|---|---|
| 栈模式 | 场景栈管理游戏状态 |
| 延迟执行 | Pending Actions 避免并发修改 |
| 模板方法 | Scene定义生命周期框架 |
| 策略模式 | 不同场景实现不同逻辑 |
我们可以有以下应用
// 主菜单场景
scene_manager_->requestPushScene(
std::make_unique<MainMenuScene>("MainMenu", context, scene_manager)
);
// 进入游戏(压入游戏场景)
scene_manager_->requestPushScene(
std::make_unique<GameScene>("Game", context, scene_manager)
);
// 暂停游戏(压入暂停场景,游戏场景继续渲染)
scene_manager_->requestPushScene(
std::make_unique<PauseScene>("Pause", context, scene_manager)
);
// 切换关卡(替换场景)
scene_manager_->requestReplaceScene(
std::make_unique<Level2Scene>("Level2", context, scene_manager)
);
// 返回主菜单(弹出所有场景,压入主菜单)
scene_manager_->requestPopScene();
遇到的问题
1.我其实不太明白为什么Scene类中有一个is_initialized_标志。
说实话我感觉有点多余,不过为了保证完整性还是保留下来了,因为Scene是由场景管理器SceneManager管理的,因为在GameApp中的init()中创建场景并发出请求。
auto scene = std::make_unique<game::scene::GameScene>("GameScene", *context_, *scene_manager_);
scene_manager_->requestPushScene(std::move(scene));
那么会在update的时候进行调用,而SceneManager的update是先进行更新当前场景然后再processPendingAction,很显然第一帧的时候获得的是空的current_scene,因为场景栈中没有任何场景,我们在处理pendingAction时接收到请求,然后才进行对于场景的操作,比如pushScene,这个时候会检查是否进行初始化,如果没有就init(),压入栈,pending_scene_为nullptr,很显然这个动作流程只会执行一次,当然防御了后续意外性的初始化情况。
scene 对象是“新创建→被 push 进栈→最终被 pop/close 销毁”的典型一次性生命周期。 is_initialized_ 的“防重复 init”并没有实际场景能发挥。我还是留着吧,不妨碍大框架,后续可能会有其他作用。
2.右值引用和一些小细节
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_);
}
可以看到这么个函数,其接收的是右值引用,这个接口要求调用者 交出所有权。临时unique_ptr(右值)可以直接绑定到&&;左值unique_ptr不能直接传,必须std::move变成右值才可以传;在函数提中game_object是具名变量,所以表达式永远是左值,因此需要std::move(game_object)才能把所有权移动进容器;vector<unique_ptr<...>> 的元素不可拷贝,只能 move,因此这是所有权转移,不是深拷贝。
请明白值类别和引用类型,这不是一个东西,传入右值,会绑定到T&&的右值引用这个引用类型上,但是这个变量game_object左值表达式,值类别是左值;传入左值,其实不能传左值,因为左值不能绑定到 && 形参;你需要用 std::move 把左值表达式转换成右值表达式(xvalue),从而允许移动其资源。
3.简单带过一下场景的设计方面细节
我们一般会使用延迟添加游戏对象来防止在遍历循环的时候添加元素/删除元素导致迭代器失效的问题,我们会准备两个gameObject容器,一个存放所有的游戏对象,一个放需要后边添加的游戏对象。然后在update更新遍历完后再进行将延时添加容器中的游戏对象加入到主容器中,这里保证一帧内的完整性,我们其实只需在update()写遍历就行,我们会给每个游戏对象加上一个是否需要remove的标志,通过在update更新遍历的时候进行删除。
4.删除游戏对象时候erase-remove惯用法失效,采用到新方法
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>& game_object){return game_object_ptr == game_object.get();});
if(it != game_objects_.end()){
(*it)->clean(); // 清理对象 传入指针,只有一个对象被删除,不需要遍历it到末尾
game_objects_.erase(it);
spdlog::trace("场景{} 移除游戏对象{}", scene_name_, game_object_ptr->getName());
} else {
spdlog::warn("场景{} 中没有找到该游戏对象",scene_name_);
}
}
由于智能指针不能和传入的参数裸指针比较,采用std::remove_if来进行判断比较。其中用到了lambda表达式,[](){},捕获参数,写入参数,函数体部分,我们需要直接写判断的逻辑,返回需要删除的游戏对象。
5.派生类的构造函数显式写出的问题。
我们的game_scene继承自scene,也就是class game::scene::GameScene final : public engine::scene::Scene。
由于 Scene 没有可用的默认构造函数(Scene()),派生类必须显式定义构造函数并在初始化列表中调用 Scene(...)。
Scene(std::string scene_name, engine::core::Context& context, engine::scene::SceneManager& sceneManager);
所以在派生类GameScene中需要显式写出构造函数,通过该方式,先调用基类的构造函数
GameScene::GameScene(std::string name, engine::core::Context &context, engine::scene::SceneManager &sceneManager)
: engine::scene::Scene(std::move(name), context, sceneManager){
spdlog::trace("GameScene 构造完成");
}
注意派生类构造顺序:
- 先构造基类子对象(调用基类构造函数)
- 再按成员声明顺序构造成员
- 最后执行派生类构造函数体

浙公网安备 33010602011771号