Engine: 实现 Components 和 Scene 模块,包含完整单元测试

新增 Components 模块:
- Component 基类 (生命周期、启用状态管理)
- TransformComponent (本地/世界空间变换、矩阵缓存、父子层级)
- GameObject (组件管理、父子层级、激活状态、静态查找)

新增 Scene 模块:
- Scene (场景管理、对象创建销毁、查找、生命周期)
- SceneManager (单例模式、多场景管理、场景切换)

新增测试:
- test_component.cpp (12 个测试)
- test_transform_component.cpp (35 个测试)
- test_game_object.cpp (26 个测试)
- test_scene.cpp (20 个测试)
- test_scene_manager.cpp (17 个测试)

所有测试均已编译通过。
This commit is contained in:
2026-03-20 20:22:04 +08:00
parent 93139813aa
commit 00f70eccf1
10 changed files with 1164 additions and 1 deletions

217
tests/Scene/test_scene.cpp Normal file
View File

@@ -0,0 +1,217 @@
#include <gtest/gtest.h>
#include <XCEngine/Scene/Scene.h>
#include <XCEngine/Components/GameObject.h>
#include <XCEngine/Components/TransformComponent.h>
using namespace XCEngine::Components;
namespace {
class TestComponent : public Component {
public:
TestComponent() = default;
explicit TestComponent(const std::string& name) : m_customName(name) {}
std::string GetName() const override {
return m_customName.empty() ? "TestComponent" : m_customName;
}
bool m_awakeCalled = false;
bool m_updateCalled = false;
void Awake() override { m_awakeCalled = true; }
void Update(float deltaTime) override { m_updateCalled = true; }
private:
std::string m_customName;
};
class SceneTest : public ::testing::Test {
protected:
void SetUp() override {
testScene = std::make_unique<Scene>("TestScene");
}
std::unique_ptr<Scene> testScene;
};
TEST(Scene_Test, DefaultConstructor_DefaultName) {
Scene s;
EXPECT_EQ(s.GetName(), "Untitled");
}
TEST(Scene_Test, NamedConstructor) {
Scene s("MyScene");
EXPECT_EQ(s.GetName(), "MyScene");
}
TEST_F(SceneTest, CreateGameObject_Simple) {
GameObject* go = testScene->CreateGameObject("TestObject");
EXPECT_NE(go, nullptr);
EXPECT_EQ(go->GetName(), "TestObject");
}
TEST_F(SceneTest, CreateGameObject_WithParent) {
GameObject* parent = testScene->CreateGameObject("Parent");
GameObject* child = testScene->CreateGameObject("Child", parent);
EXPECT_NE(child, nullptr);
EXPECT_EQ(child->GetParent(), parent);
}
TEST_F(SceneTest, CreateGameObject_AssignsID) {
GameObject* go1 = testScene->CreateGameObject("Object1");
GameObject* go2 = testScene->CreateGameObject("Object2");
EXPECT_NE(go1->GetID(), GameObject::INVALID_ID);
EXPECT_NE(go2->GetID(), GameObject::INVALID_ID);
EXPECT_NE(go1->GetID(), go2->GetID());
}
TEST_F(SceneTest, CreateGameObject_AddsToRoot) {
GameObject* go = testScene->CreateGameObject("RootObject");
auto roots = testScene->GetRootGameObjects();
EXPECT_EQ(roots.size(), 1u);
EXPECT_EQ(roots[0], go);
}
TEST_F(SceneTest, DestroyGameObject_Simple) {
GameObject* go = testScene->CreateGameObject("TestObject");
testScene->DestroyGameObject(go);
EXPECT_EQ(testScene->Find("TestObject"), nullptr);
}
TEST_F(SceneTest, DestroyGameObject_WithChildren) {
GameObject* parent = testScene->CreateGameObject("Parent");
testScene->CreateGameObject("Child", parent);
testScene->DestroyGameObject(parent);
EXPECT_EQ(testScene->Find("Parent"), nullptr);
EXPECT_EQ(testScene->Find("Child"), nullptr);
}
TEST_F(SceneTest, Find_Exists) {
testScene->CreateGameObject("FindMe");
GameObject* found = testScene->Find("FindMe");
EXPECT_NE(found, nullptr);
EXPECT_EQ(found->GetName(), "FindMe");
}
TEST_F(SceneTest, Find_NotExists) {
GameObject* found = testScene->Find("NonExistent");
EXPECT_EQ(found, nullptr);
}
TEST_F(SceneTest, GetRootGameObjects_ReturnsTopLevel) {
GameObject* parent = testScene->CreateGameObject("Parent");
testScene->CreateGameObject("Child", parent);
auto roots = testScene->GetRootGameObjects();
EXPECT_EQ(roots.size(), 1u);
EXPECT_EQ(roots[0], parent);
}
TEST(Scene_Test, GetRootGameObjects_EmptyScene) {
Scene emptyScene;
auto roots = emptyScene.GetRootGameObjects();
EXPECT_EQ(roots.size(), 0u);
}
TEST_F(SceneTest, Update_UpdatesActiveObjects) {
GameObject* go = testScene->CreateGameObject("TestObject");
TestComponent* comp = go->AddComponent<TestComponent>();
testScene->Update(0.016f);
EXPECT_TRUE(comp->m_updateCalled);
}
TEST_F(SceneTest, Update_SkipsInactiveObjects) {
GameObject* go = testScene->CreateGameObject("InactiveObject");
TestComponent* comp = go->AddComponent<TestComponent>();
go->SetActive(false);
testScene->Update(0.016f);
EXPECT_FALSE(comp->m_updateCalled);
}
TEST(Scene_Test, IsActive_DefaultTrue) {
Scene s;
EXPECT_TRUE(s.IsActive());
}
TEST(Scene_Test, SetActive) {
Scene s;
s.SetActive(false);
EXPECT_FALSE(s.IsActive());
}
TEST_F(SceneTest, FindObjectOfType) {
GameObject* go = testScene->CreateGameObject("TestObject");
go->AddComponent<TestComponent>();
TestComponent* found = testScene->FindObjectOfType<TestComponent>();
EXPECT_NE(found, nullptr);
}
TEST_F(SceneTest, FindObjectsOfType) {
GameObject* go1 = testScene->CreateGameObject("Object1");
GameObject* go2 = testScene->CreateGameObject("Object2");
go1->AddComponent<TestComponent>();
go2->AddComponent<TestComponent>();
auto found = testScene->FindObjectsOfType<TestComponent>();
EXPECT_EQ(found.size(), 2u);
}
TEST_F(SceneTest, FindGameObjectWithTag) {
GameObject* go = testScene->CreateGameObject("MyTag");
GameObject* found = testScene->FindGameObjectWithTag("MyTag");
EXPECT_NE(found, nullptr);
}
TEST_F(SceneTest, OnGameObjectCreated_Event) {
bool eventFired = false;
testScene->OnGameObjectCreated().Subscribe([&eventFired](GameObject*) {
eventFired = true;
});
testScene->CreateGameObject("TestObject");
EXPECT_TRUE(eventFired);
}
TEST_F(SceneTest, OnGameObjectDestroyed_Event) {
bool eventFired = false;
testScene->OnGameObjectDestroyed().Subscribe([&eventFired](GameObject*) {
eventFired = true;
});
GameObject* go = testScene->CreateGameObject("TestObject");
testScene->DestroyGameObject(go);
EXPECT_TRUE(eventFired);
}
} // namespace