From 84741a45ac16d037b7f966a0b87fcdb04dd6a98d Mon Sep 17 00:00:00 2001 From: Ziyu Date: Sat, 24 May 2025 11:00:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=B8=B8=E6=88=8F=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E4=B8=8E=E7=BB=84=E4=BB=B6=E5=9F=BA=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 1 + src/engine/component/component.h | 43 ++++++++++ src/engine/core/game_app.cpp | 8 ++ src/engine/core/game_app.h | 1 + src/engine/object/game_object.cpp | 44 ++++++++++ src/engine/object/game_object.h | 128 ++++++++++++++++++++++++++++++ src/main.cpp | 2 +- 7 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 src/engine/component/component.h create mode 100644 src/engine/object/game_object.cpp create mode 100644 src/engine/object/game_object.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8aab540..49bdebc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ add_executable(${TARGET} src/engine/render/renderer.cpp src/engine/render/camera.cpp src/engine/input/input_manager.cpp + src/engine/object/game_object.cpp ) # 链接库 diff --git a/src/engine/component/component.h b/src/engine/component/component.h new file mode 100644 index 0000000..c119db3 --- /dev/null +++ b/src/engine/component/component.h @@ -0,0 +1,43 @@ +#pragma once +// 前置声明 +namespace engine::object { + class GameObject; +} + +namespace engine::component { + +/** + * @brief 组件的抽象基类。 + * + * 所有具体组件都应从此类继承。 + * 定义了组件生命周期中可能调用的通用方法。 + */ +class Component { + friend class engine::object::GameObject; // 它需要调用Component的init方法 + +protected: + engine::object::GameObject* owner_ = nullptr; ///< @brief 指向拥有此组件的 GameObject + +public: + Component() = default; + virtual ~Component() = default; ///< @brief 虚析构函数确保正确清理派生类 + + // 禁止拷贝和移动,组件通常不应被拷贝或移动(更改owner_就相当于移动) + Component(const Component&) = delete; + Component& operator=(const Component&) = delete; + Component(Component&&) = delete; + Component& operator=(Component&&) = delete; + + void setOwner(engine::object::GameObject* owner) { owner_ = owner; } ///< @brief 设置拥有此组件的 GameObject + engine::object::GameObject* getOwner() const { return owner_; } ///< @brief 获取拥有此组件的 GameObject + +protected: + // 关键循环函数,全部设为保护,只有 GameObject 需要(可以)调用 (未来将其中一个改为 = 0 以实现抽象类) + virtual void init() {} ///< @brief 保留两段初始化的机制,GameObject 添加组件时自动调用,不需要外部调用 + virtual void handleInput() {} ///< @brief 处理输入 + virtual void update(float) {} ///< @brief 更新 + virtual void render() {} ///< @brief 渲染 + virtual void clean() {} ///< @brief 清理 +}; + +} // namespace engine::component \ No newline at end of file diff --git a/src/engine/core/game_app.cpp b/src/engine/core/game_app.cpp index 21d1258..a7042c6 100644 --- a/src/engine/core/game_app.cpp +++ b/src/engine/core/game_app.cpp @@ -4,6 +4,7 @@ #include "../render/renderer.h" #include "../render/camera.h" #include "../input/input_manager.h" +#include "../object/game_object.h" #include "config.h" #include #include @@ -55,6 +56,7 @@ bool GameApp::init() { is_running_ = true; spdlog::trace("GameApp 初始化成功。"); + testGameObject(); return true; } @@ -266,4 +268,10 @@ void GameApp::testInputManager() } } +void GameApp::testGameObject() +{ + engine::object::GameObject game_object("test_game_object"); + game_object.addComponent(); +} + } // namespace engine::core \ No newline at end of file diff --git a/src/engine/core/game_app.h b/src/engine/core/game_app.h index 729c4c1..0f16782 100644 --- a/src/engine/core/game_app.h +++ b/src/engine/core/game_app.h @@ -74,6 +74,7 @@ private: void testRenderer(); void testCamera(); void testInputManager(); + void testGameObject(); }; } // namespace engine::core diff --git a/src/engine/object/game_object.cpp b/src/engine/object/game_object.cpp new file mode 100644 index 0000000..96bf26e --- /dev/null +++ b/src/engine/object/game_object.cpp @@ -0,0 +1,44 @@ +#include "game_object.h" +#include "../render/renderer.h" +#include "../input/input_manager.h" +#include "../render/camera.h" +#include + +namespace engine::object { +GameObject::GameObject(const std::string &name, const std::string &tag): name_(name), tag_(tag) +{ + spdlog::trace("GameObject created: {} {}", name_, tag_); +} + +void GameObject::update(float delta_time) { + // 遍历所有组件并调用它们的 update 方法 + for (auto& pair : components_) { + pair.second->update(delta_time); + } +} + +void GameObject::render() { + // 遍历所有组件并调用它们的 render 方法 + for (auto& pair : components_) { + pair.second->render(); + } +} + +void GameObject::clean() { + spdlog::trace("Cleaning GameObject..."); + // 遍历所有组件并调用它们的 clean 方法 + for (auto& pair : components_) { + pair.second->clean(); + } + components_.clear(); // 清空 map, unique_ptr 会自动释放内存 +} + +void GameObject::handleInput() { + // 遍历所有组件并调用它们的 handleInput 方法 + for (auto& pair : components_) { + pair.second->handleInput(); + } +} + + +} // namespace engine::object \ No newline at end of file diff --git a/src/engine/object/game_object.h b/src/engine/object/game_object.h new file mode 100644 index 0000000..4e22dae --- /dev/null +++ b/src/engine/object/game_object.h @@ -0,0 +1,128 @@ +#pragma once +#include "../component/component.h" +#include +#include +#include // 用于类型索引 +#include // 用于完美转发 +#include + +namespace engine::object { + +/** + * @brief 游戏对象类,负责管理游戏对象的组件。 + * + * 该类管理游戏对象的组件,并提供添加、获取、检查和移除组件的功能。 + * 它还提供更新和渲染游戏对象的方法。 + */ +class GameObject final { +private: + std::string name_; ///< @brief 名称 + std::string tag_; ///< @brief 标签 + std::unordered_map> components_; ///< @brief 组件列表 + bool need_remove_ = false; ///< @brief 延迟删除的标识,将来由场景类负责删除 + +public: + + GameObject(const std::string& name = "", const std::string& tag = ""); ///< @brief 构造函数。默认名称为空,标签为空 + + // 禁止拷贝和移动,确保唯一性 (通常游戏对象不应随意拷贝) + GameObject(const GameObject&) = delete; + GameObject& operator=(const GameObject&) = delete; + GameObject(GameObject&&) = delete; + GameObject& operator=(GameObject&&) = delete; + + // setters and getters + void setName(const std::string& name) { name_ = name; } ///< @brief 设置名称 + const std::string& getName() const { return name_; } ///< @brief 获取名称 + void setTag(const std::string& tag) { tag_ = tag; } ///< @brief 设置标签 + const std::string& getTag() const { return tag_; } ///< @brief 获取标签 + void setNeedRemove(bool need_remove) { need_remove_ = need_remove; } ///< @brief 设置是否需要删除 + bool isNeedRemove() const { return need_remove_; } ///< @brief 获取是否需要删除 + + /** + * @brief 添加组件 (里面会完成组件的init()) + * + * @tparam T 组件类型 + * @tparam Args 组件构造函数参数类型 + * @param args 组件构造函数参数 + * @return 组件指针 + */ + template + T* addComponent(Args&&... args) { + // 检测组件是否合法。 /* static_assert(condition, message):静态断言,在编译期检测,无任何性能影响 */ + /* std::is_base_of::value -- 判断 Base 类型是否是 Derived 类型的基类 */ + static_assert(std::is_base_of::value, "T 必须继承自 Component"); + // 获取类型标识。 /* typeid(T) -- 用于获取一个表达式或类型的运行时类型信息 (RTTI), 返回 std::type_info& */ + /* std::type_index -- 针对std::type_info对象的包装器,主要设计用来作为关联容器(如 std::map)的键。*/ + auto type_index = std::type_index(typeid(T)); + // 如果组件已经存在,则直接返回组件指针 + if (hasComponent()) { + return getComponent(); + } + // 如果不存在则创建组件 /* std::forward -- 用于实现完美转发。传递多个参数的时候使用...标识 */ + auto new_component = std::make_unique(std::forward(args)...); + T* ptr = new_component.get(); // 先获取裸指针以便返回 + new_component->setOwner(this); // 设置组件的拥有者 + components_[type_index] = std::move(new_component); // 移动组件 (new_component 变为空,不可再使用) + ptr->init(); // 初始化组件 (因此必须用ptr而不能用new_component) + spdlog::debug("GameObject::addComponent: {} added component {}", name_, typeid(T).name()); + return ptr; // 返回非拥有指针 + } + + /** + * @brief 获取组件 + * + * @tparam T 组件类型 + * @return 组件指针 + */ + template + T* getComponent() const { + static_assert(std::is_base_of::value, "T 必须继承自 Component"); + auto type_index = std::type_index(typeid(T)); + auto it = components_.find(type_index); + if (it != components_.end()) { + // 返回unique_ptr的裸指针。(肯定是T类型, static_cast其实并无必要,但保留可以使我们意图更清晰) + return static_cast(it->second.get()); + } + return nullptr; + } + + /** + * @brief 检查是否存在组件 + * + * @tparam T 组件类型 + * @return 是否存在组件 + */ + template + bool hasComponent() const { + static_assert(std::is_base_of::value, "T 必须继承自 Component"); + // contains方法为 C++20 新增 + return components_.contains(std::type_index(typeid(T))); + } + + /** + * @brief 移除组件 + * + * @tparam T 组件类型 + */ + template + void removeComponent() { + static_assert(std::is_base_of::value, "T 必须继承自 Component"); + auto type_index = std::type_index(typeid(T)); + auto it = components_.find(type_index); + if (it != components_.end()) { + it->second->clean(); + components_.erase(it); + } + } + + // 关键循环函数 + void update(float delta_time); ///< @brief 更新所有组件 + void render(); ///< @brief 渲染所有组件 + void clean(); ///< @brief 清理所有组件 + void handleInput(); ///< @brief 处理输入 + +}; + +} // namespace engine::object + diff --git a/src/main.cpp b/src/main.cpp index 738b873..ce76ce5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,7 @@ #include int main(int /* argc */, char* /* argv */[]) { - // spdlog::set_level(spdlog::level::debug); + spdlog::set_level(spdlog::level::debug); engine::core::GameApp app; app.run();