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:
28
tests/Scene/CMakeLists.txt
Normal file
28
tests/Scene/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
project(XCEngine_SceneTests)
|
||||
|
||||
set(SCENE_TEST_SOURCES
|
||||
test_scene.cpp
|
||||
test_scene_manager.cpp
|
||||
)
|
||||
|
||||
add_executable(xcengine_scene_tests ${SCENE_TEST_SOURCES})
|
||||
|
||||
if(MSVC)
|
||||
set_target_properties(xcengine_scene_tests PROPERTIES
|
||||
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(xcengine_scene_tests PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(xcengine_scene_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
add_test(NAME SceneTests COMMAND xcengine_scene_tests)
|
||||
217
tests/Scene/test_scene.cpp
Normal file
217
tests/Scene/test_scene.cpp
Normal 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
|
||||
172
tests/Scene/test_scene_manager.cpp
Normal file
172
tests/Scene/test_scene_manager.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Scene/SceneManager.h>
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
|
||||
namespace {
|
||||
|
||||
class SceneManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
void TearDown() override {
|
||||
auto scenes = SceneManager::Get().GetAllScenes();
|
||||
for (auto* scene : scenes) {
|
||||
SceneManager::Get().UnloadScene(scene);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SceneManager_Test, Get_ReturnsSingleton) {
|
||||
SceneManager& sm1 = SceneManager::Get();
|
||||
SceneManager& sm2 = SceneManager::Get();
|
||||
|
||||
EXPECT_EQ(&sm1, &sm2);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, CreateScene_Simple) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
|
||||
Scene* scene = sm.CreateScene("TestScene");
|
||||
|
||||
EXPECT_NE(scene, nullptr);
|
||||
EXPECT_EQ(scene->GetName(), "TestScene");
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, CreateScene_AssignsName) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
|
||||
Scene* scene = sm.CreateScene("MyScene");
|
||||
|
||||
EXPECT_EQ(scene->GetName(), "MyScene");
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, GetScene_Exists) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
sm.CreateScene("TestScene");
|
||||
|
||||
Scene* found = sm.GetScene("TestScene");
|
||||
|
||||
EXPECT_NE(found, nullptr);
|
||||
EXPECT_EQ(found->GetName(), "TestScene");
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, GetScene_NotExists) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
|
||||
Scene* found = sm.GetScene("NonExistent");
|
||||
|
||||
EXPECT_EQ(found, nullptr);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, GetAllScenes_ReturnsAll) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
sm.CreateScene("Scene1");
|
||||
sm.CreateScene("Scene2");
|
||||
sm.CreateScene("Scene3");
|
||||
|
||||
auto scenes = sm.GetAllScenes();
|
||||
|
||||
EXPECT_EQ(scenes.size(), 3u);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, SetActiveScene) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
Scene* scene1 = sm.CreateScene("Scene1");
|
||||
Scene* scene2 = sm.CreateScene("Scene2");
|
||||
|
||||
sm.SetActiveScene(scene2);
|
||||
|
||||
EXPECT_EQ(sm.GetActiveScene(), scene2);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, SetActiveScene_ByName) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
sm.CreateScene("Scene1");
|
||||
sm.CreateScene("Scene2");
|
||||
|
||||
sm.SetActiveScene("Scene2");
|
||||
|
||||
EXPECT_EQ(sm.GetActiveScene()->GetName(), "Scene2");
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, GetActiveScene_InitiallyFirst) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
auto scenes = sm.GetAllScenes();
|
||||
for (auto* scene : scenes) {
|
||||
sm.UnloadScene(scene);
|
||||
}
|
||||
|
||||
Scene* scene1 = sm.CreateScene("First");
|
||||
|
||||
EXPECT_EQ(sm.GetActiveScene(), scene1);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, UnloadScene) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
Scene* scene = sm.CreateScene("ToUnload");
|
||||
|
||||
sm.UnloadScene(scene);
|
||||
|
||||
EXPECT_EQ(sm.GetScene("ToUnload"), nullptr);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, UnloadScene_ByName) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
sm.CreateScene("ToUnload");
|
||||
|
||||
sm.UnloadScene("ToUnload");
|
||||
|
||||
EXPECT_EQ(sm.GetScene("ToUnload"), nullptr);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, OnSceneLoaded_Event) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
bool eventFired = false;
|
||||
sm.OnSceneLoaded().Subscribe([&eventFired](Scene*) {
|
||||
eventFired = true;
|
||||
});
|
||||
|
||||
sm.CreateScene("TestScene");
|
||||
|
||||
EXPECT_TRUE(eventFired);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, OnActiveSceneChanged_Event) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
Scene* scene1 = sm.CreateScene("Scene1");
|
||||
Scene* scene2 = sm.CreateScene("Scene2");
|
||||
|
||||
bool eventFired = false;
|
||||
sm.OnActiveSceneChanged().Subscribe([&eventFired](Scene*) {
|
||||
eventFired = true;
|
||||
});
|
||||
|
||||
sm.SetActiveScene(scene2);
|
||||
|
||||
EXPECT_TRUE(eventFired);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, CreateScene_MultipleScenes) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
|
||||
Scene* scene1 = sm.CreateScene("Scene1");
|
||||
Scene* scene2 = sm.CreateScene("Scene2");
|
||||
Scene* scene3 = sm.CreateScene("Scene3");
|
||||
|
||||
EXPECT_NE(scene1, scene2);
|
||||
EXPECT_NE(scene2, scene3);
|
||||
EXPECT_NE(scene1, scene3);
|
||||
}
|
||||
|
||||
TEST(SceneManager_Test, SetActiveScene_ToNullptr) {
|
||||
SceneManager& sm = SceneManager::Get();
|
||||
Scene* scene1 = sm.CreateScene("Scene1");
|
||||
Scene* scene2 = sm.CreateScene("Scene2");
|
||||
sm.SetActiveScene(scene2);
|
||||
|
||||
sm.SetActiveScene(nullptr);
|
||||
|
||||
EXPECT_EQ(sm.GetActiveScene(), nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user