完成游戏对象与组件基类

This commit is contained in:
Ziyu
2025-06-16 14:44:47 +08:00
parent a7334b2ccb
commit 84741a45ac
7 changed files with 226 additions and 1 deletions
+1
View File
@@ -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
)
# 链接库
+43
View File
@@ -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
+8
View File
@@ -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 <SDL3/SDL.h>
#include <spdlog/spdlog.h>
@@ -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<engine::component::Component>();
}
} // namespace engine::core
+1
View File
@@ -74,6 +74,7 @@ private:
void testRenderer();
void testCamera();
void testInputManager();
void testGameObject();
};
} // namespace engine::core
+44
View File
@@ -0,0 +1,44 @@
#include "game_object.h"
#include "../render/renderer.h"
#include "../input/input_manager.h"
#include "../render/camera.h"
#include <spdlog/spdlog.h>
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
+128
View File
@@ -0,0 +1,128 @@
#pragma once
#include "../component/component.h"
#include <memory>
#include <unordered_map>
#include <typeindex> // 用于类型索引
#include <utility> // 用于完美转发
#include <spdlog/spdlog.h>
namespace engine::object {
/**
* @brief 游戏对象类,负责管理游戏对象的组件。
*
* 该类管理游戏对象的组件,并提供添加、获取、检查和移除组件的功能。
* 它还提供更新和渲染游戏对象的方法。
*/
class GameObject final {
private:
std::string name_; ///< @brief 名称
std::string tag_; ///< @brief 标签
std::unordered_map<std::type_index, std::unique_ptr<engine::component::Component>> 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 <typename T, typename... Args>
T* addComponent(Args&&... args) {
// 检测组件是否合法。 /* static_assert(condition, message):静态断言,在编译期检测,无任何性能影响 */
/* std::is_base_of<Base, Derived>::value -- 判断 Base 类型是否是 Derived 类型的基类 */
static_assert(std::is_base_of<engine::component::Component, T>::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<T>()) {
return getComponent<T>();
}
// 如果不存在则创建组件 /* std::forward -- 用于实现完美转发。传递多个参数的时候使用...标识 */
auto new_component = std::make_unique<T>(std::forward<Args>(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 <typename T>
T* getComponent() const {
static_assert(std::is_base_of<engine::component::Component, T>::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<T*>(it->second.get());
}
return nullptr;
}
/**
* @brief 检查是否存在组件
*
* @tparam T 组件类型
* @return 是否存在组件
*/
template <typename T>
bool hasComponent() const {
static_assert(std::is_base_of<engine::component::Component, T>::value, "T 必须继承自 Component");
// contains方法为 C++20 新增
return components_.contains(std::type_index(typeid(T)));
}
/**
* @brief 移除组件
*
* @tparam T 组件类型
*/
template <typename T>
void removeComponent() {
static_assert(std::is_base_of<engine::component::Component, T>::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
+1 -1
View File
@@ -2,7 +2,7 @@
#include <spdlog/spdlog.h>
int main(int /* argc */, char* /* argv */[]) {
// spdlog::set_level(spdlog::level::debug);
spdlog::set_level(spdlog::level::debug);
engine::core::GameApp app;
app.run();