完成游戏对象与组件基类
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
# 链接库
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -74,6 +74,7 @@ private:
|
||||
void testRenderer();
|
||||
void testCamera();
|
||||
void testInputManager();
|
||||
void testGameObject();
|
||||
};
|
||||
|
||||
} // namespace engine::core
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user