feat(scripting): add script add-component api

This commit is contained in:
2026-03-27 15:32:37 +08:00
parent 9c94adb4a2
commit f0d6d4f41c
10 changed files with 401 additions and 6 deletions

View File

@@ -15,14 +15,20 @@ std::string ToStdString(const Containers::String& value) {
std::vector<std::string> SplitMaterialPaths(const std::string& value) {
std::vector<std::string> paths;
std::stringstream stream(value);
std::string item;
while (std::getline(stream, item, '|')) {
paths.push_back(item);
if (value.empty()) {
return paths;
}
if (value.empty()) {
paths.clear();
size_t start = 0;
while (true) {
const size_t separator = value.find('|', start);
if (separator == std::string::npos) {
paths.push_back(value.substr(start));
break;
}
paths.push_back(value.substr(start, separator - start));
start = separator + 1;
}
return paths;

View File

@@ -221,6 +221,37 @@ bool HasNativeComponent(Components::GameObject* gameObject, ManagedComponentKind
return false;
}
Components::Component* AddOrGetNativeComponent(Components::GameObject* gameObject, ManagedComponentKind componentKind) {
if (!gameObject) {
return nullptr;
}
switch (componentKind) {
case ManagedComponentKind::Transform:
return gameObject->GetTransform();
case ManagedComponentKind::Camera:
return gameObject->GetComponent<Components::CameraComponent>()
? static_cast<Components::Component*>(gameObject->GetComponent<Components::CameraComponent>())
: static_cast<Components::Component*>(gameObject->AddComponent<Components::CameraComponent>());
case ManagedComponentKind::Light:
return gameObject->GetComponent<Components::LightComponent>()
? static_cast<Components::Component*>(gameObject->GetComponent<Components::LightComponent>())
: static_cast<Components::Component*>(gameObject->AddComponent<Components::LightComponent>());
case ManagedComponentKind::MeshFilter:
return gameObject->GetComponent<Components::MeshFilterComponent>()
? static_cast<Components::Component*>(gameObject->GetComponent<Components::MeshFilterComponent>())
: static_cast<Components::Component*>(gameObject->AddComponent<Components::MeshFilterComponent>());
case ManagedComponentKind::MeshRenderer:
return gameObject->GetComponent<Components::MeshRendererComponent>()
? static_cast<Components::Component*>(gameObject->GetComponent<Components::MeshRendererComponent>())
: static_cast<Components::Component*>(gameObject->AddComponent<Components::MeshRendererComponent>());
case ManagedComponentKind::Unknown:
return nullptr;
}
return nullptr;
}
Components::CameraComponent* FindCameraComponent(uint64_t gameObjectUUID) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return gameObject ? gameObject->GetComponent<Components::CameraComponent>() : nullptr;
@@ -318,6 +349,11 @@ uint64_t InternalCall_GameObject_GetComponent(uint64_t gameObjectUUID, MonoRefle
return gameObjectUUID;
}
uint64_t InternalCall_GameObject_AddComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) {
Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID);
return AddOrGetNativeComponent(gameObject, ResolveManagedComponentKind(componentType)) ? gameObjectUUID : 0;
}
mono_bool InternalCall_Behaviour_GetEnabled(uint64_t scriptComponentUUID) {
ScriptComponent* component = FindScriptComponentByUUID(scriptComponentUUID);
return (component && component->IsEnabled()) ? 1 : 0;
@@ -955,6 +991,7 @@ void RegisterInternalCalls() {
mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetActive", reinterpret_cast<const void*>(&InternalCall_GameObject_SetActive));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_HasComponent", reinterpret_cast<const void*>(&InternalCall_GameObject_HasComponent));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetComponent", reinterpret_cast<const void*>(&InternalCall_GameObject_GetComponent));
mono_add_internal_call("XCEngine.InternalCalls::GameObject_AddComponent", reinterpret_cast<const void*>(&InternalCall_GameObject_AddComponent));
mono_add_internal_call("XCEngine.InternalCalls::Behaviour_GetEnabled", reinterpret_cast<const void*>(&InternalCall_Behaviour_GetEnabled));
mono_add_internal_call("XCEngine.InternalCalls::Behaviour_SetEnabled", reinterpret_cast<const void*>(&InternalCall_Behaviour_SetEnabled));
mono_add_internal_call("XCEngine.InternalCalls::Transform_GetLocalPosition", reinterpret_cast<const void*>(&InternalCall_Transform_GetLocalPosition));

View File

@@ -71,9 +71,11 @@ set(XCENGINE_SCRIPT_CORE_SOURCES
set(XCENGINE_GAME_SCRIPT_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/BuiltinComponentProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/AddComponentProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/HierarchyProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/LifecycleProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshComponentProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshRendererEdgeCaseProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformConversionProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformMotionProbe.cs
${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformOrientationProbe.cs
@@ -134,3 +136,4 @@ add_custom_target(
${XCENGINE_GAME_SCRIPTS_DLL}
${XCENGINE_MANAGED_OUTPUT_DIR}/mscorlib.dll
)

View File

@@ -0,0 +1,87 @@
using XCEngine;
namespace Gameplay
{
public sealed class AddComponentProbe : MonoBehaviour
{
public bool InitialHasCamera;
public bool InitialHasLight;
public bool InitialHasMeshFilter;
public bool InitialHasMeshRenderer;
public bool AddedTransform;
public bool AddedCamera;
public bool AddedLight;
public bool AddedMeshFilter;
public bool AddedMeshRenderer;
public bool HasCameraAfterAdd;
public bool HasLightAfterAdd;
public bool HasMeshFilterAfterAdd;
public bool HasMeshRendererAfterAdd;
public bool CameraLookupSucceeded;
public bool LightLookupSucceeded;
public bool MeshFilterLookupSucceeded;
public bool MeshRendererLookupSucceeded;
public float ObservedCameraFieldOfView;
public float ObservedLightIntensity;
public string ObservedMeshPath = string.Empty;
public int ObservedMaterialCount;
public string ObservedMaterial0Path = string.Empty;
public int ObservedRenderLayer;
public void Start()
{
InitialHasCamera = HasComponent<Camera>();
InitialHasLight = HasComponent<Light>();
InitialHasMeshFilter = HasComponent<MeshFilter>();
InitialHasMeshRenderer = HasComponent<MeshRenderer>();
AddedTransform = AddComponent<Transform>() != null;
AddedCamera = AddComponent<Camera>() != null;
AddedLight = gameObject.AddComponent<Light>() != null;
AddedMeshFilter = AddComponent<MeshFilter>() != null;
AddedMeshRenderer = gameObject.AddComponent<MeshRenderer>() != null;
gameObject.AddComponent<Camera>();
AddComponent<Light>();
gameObject.AddComponent<MeshFilter>();
AddComponent<MeshRenderer>();
HasCameraAfterAdd = HasComponent<Camera>();
HasLightAfterAdd = HasComponent<Light>();
HasMeshFilterAfterAdd = HasComponent<MeshFilter>();
HasMeshRendererAfterAdd = HasComponent<MeshRenderer>();
CameraLookupSucceeded = TryGetComponent(out Camera camera);
LightLookupSucceeded = TryGetComponent(out Light light);
MeshFilterLookupSucceeded = TryGetComponent(out MeshFilter meshFilter);
MeshRendererLookupSucceeded = TryGetComponent(out MeshRenderer meshRenderer);
if (camera != null)
{
camera.fieldOfView = 82.0f;
ObservedCameraFieldOfView = camera.fieldOfView;
}
if (light != null)
{
light.intensity = 4.5f;
ObservedLightIntensity = light.intensity;
}
if (meshFilter != null)
{
meshFilter.meshPath = "Meshes/added.mesh";
ObservedMeshPath = meshFilter.meshPath;
}
if (meshRenderer != null)
{
meshRenderer.SetMaterialPath(0, "Materials/added.mat");
meshRenderer.renderLayer = 6;
ObservedMaterialCount = meshRenderer.materialCount;
ObservedMaterial0Path = meshRenderer.GetMaterialPath(0);
ObservedRenderLayer = meshRenderer.renderLayer;
}
}
}
}

View File

@@ -0,0 +1,51 @@
using XCEngine;
namespace Gameplay
{
public sealed class MeshRendererEdgeCaseProbe : MonoBehaviour
{
public int ObservedInitialMaterialCount;
public string ObservedNegativeIndexPath = string.Empty;
public string ObservedOutOfRangePathBeforeClear = string.Empty;
public string ObservedMaterial0PathAfterNegativeWrite = string.Empty;
public string ObservedMaterial1PathAfterNegativeWrite = string.Empty;
public int ObservedMaterialCountAfterNegativeWrite;
public int ObservedRenderLayerAfterNegativeWrite;
public int ObservedMaterialCountAfterClear;
public string ObservedMaterial0PathAfterClear = string.Empty;
public string ObservedMaterial3PathAfterClear = string.Empty;
public bool ObservedCastShadowsAfterClear;
public bool ObservedReceiveShadowsAfterClear;
public int ObservedRenderLayerAfterClear;
public void Start()
{
if (!TryGetComponent(out MeshRenderer meshRenderer) || meshRenderer == null)
{
return;
}
ObservedInitialMaterialCount = meshRenderer.materialCount;
ObservedNegativeIndexPath = meshRenderer.GetMaterialPath(-1);
ObservedOutOfRangePathBeforeClear = meshRenderer.GetMaterialPath(3);
meshRenderer.SetMaterialPath(-1, "Materials/ignored.mat");
ObservedMaterial0PathAfterNegativeWrite = meshRenderer.GetMaterialPath(0);
ObservedMaterial1PathAfterNegativeWrite = meshRenderer.GetMaterialPath(1);
ObservedMaterialCountAfterNegativeWrite = meshRenderer.materialCount;
meshRenderer.renderLayer = -5;
ObservedRenderLayerAfterNegativeWrite = meshRenderer.renderLayer;
meshRenderer.ClearMaterials();
ObservedMaterialCountAfterClear = meshRenderer.materialCount;
ObservedMaterial0PathAfterClear = meshRenderer.GetMaterialPath(0);
ObservedMaterial3PathAfterClear = meshRenderer.GetMaterialPath(3);
ObservedCastShadowsAfterClear = meshRenderer.castShadows;
ObservedReceiveShadowsAfterClear = meshRenderer.receiveShadows;
ObservedRenderLayerAfterClear = meshRenderer.renderLayer;
}
}
}

View File

@@ -34,6 +34,11 @@ namespace XCEngine
return GameObject.GetComponent<T>();
}
public T AddComponent<T>() where T : Component
{
return GameObject.AddComponent<T>();
}
public bool TryGetComponent<T>(out T component) where T : Component
{
component = GetComponent<T>();

View File

@@ -46,6 +46,12 @@ namespace XCEngine
return Component.Create<T>(componentOwnerUUID);
}
public T AddComponent<T>() where T : Component
{
ulong componentOwnerUUID = InternalCalls.GameObject_AddComponent(UUID, typeof(T));
return Component.Create<T>(componentOwnerUUID);
}
public bool TryGetComponent<T>(out T component) where T : Component
{
component = GetComponent<T>();

View File

@@ -38,6 +38,9 @@ namespace XCEngine
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern ulong GameObject_GetComponent(ulong gameObjectUUID, Type componentType);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern ulong GameObject_AddComponent(ulong gameObjectUUID, Type componentType);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool Behaviour_GetEnabled(ulong scriptComponentUUID);

View File

@@ -135,6 +135,34 @@ TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesMaterialPathsAn
delete material1;
}
TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesTrailingEmptyMaterialSlots) {
MeshRendererComponent source;
Material* material0 = CreateTestMaterial("M0", "Materials/serialized0.mat");
source.SetMaterial(0, material0);
source.SetMaterialPath(1, "");
source.SetCastShadows(false);
source.SetReceiveShadows(true);
source.SetRenderLayer(9);
std::stringstream stream;
source.Serialize(stream);
MeshRendererComponent target;
target.Deserialize(stream);
ASSERT_EQ(target.GetMaterialCount(), 2u);
EXPECT_EQ(target.GetMaterial(0), nullptr);
EXPECT_EQ(target.GetMaterial(1), nullptr);
EXPECT_EQ(target.GetMaterialPath(0), "Materials/serialized0.mat");
EXPECT_EQ(target.GetMaterialPath(1), "");
EXPECT_FALSE(target.GetCastShadows());
EXPECT_TRUE(target.GetReceiveShadows());
EXPECT_EQ(target.GetRenderLayer(), 9u);
source.ClearMaterials();
delete material0;
}
TEST(MeshRendererComponent_Test, SetMaterialPathPreservesPathWithoutLoadedResource) {
MeshRendererComponent component;

View File

@@ -393,6 +393,175 @@ TEST_F(MonoScriptRuntimeTest, ManagedMeshComponentWrappersReadAndWritePathsAndFl
EXPECT_EQ(meshRenderer->GetRenderLayer(), 11u);
}
TEST_F(MonoScriptRuntimeTest, ManagedMeshRendererWrapperHandlesClearAndBoundaryCases) {
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
GameObject* host = runtimeScene->CreateGameObject("Host");
MeshRendererComponent* meshRenderer = host->AddComponent<MeshRendererComponent>();
ScriptComponent* component = AddScript(host, "Gameplay", "MeshRendererEdgeCaseProbe");
meshRenderer->SetMaterialPath(0, "Materials/initial0.mat");
meshRenderer->SetMaterialPath(1, "Materials/initial1.mat");
meshRenderer->SetCastShadows(false);
meshRenderer->SetReceiveShadows(true);
meshRenderer->SetRenderLayer(9);
engine->OnRuntimeStart(runtimeScene);
engine->OnUpdate(0.016f);
int32_t observedInitialMaterialCount = 0;
std::string observedNegativeIndexPath;
std::string observedOutOfRangePathBeforeClear;
std::string observedMaterial0PathAfterNegativeWrite;
std::string observedMaterial1PathAfterNegativeWrite;
int32_t observedMaterialCountAfterNegativeWrite = 0;
int32_t observedRenderLayerAfterNegativeWrite = -1;
int32_t observedMaterialCountAfterClear = -1;
std::string observedMaterial0PathAfterClear;
std::string observedMaterial3PathAfterClear;
bool observedCastShadowsAfterClear = true;
bool observedReceiveShadowsAfterClear = false;
int32_t observedRenderLayerAfterClear = -1;
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMaterialCount", observedInitialMaterialCount));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedNegativeIndexPath", observedNegativeIndexPath));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedOutOfRangePathBeforeClear", observedOutOfRangePathBeforeClear));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0PathAfterNegativeWrite", observedMaterial0PathAfterNegativeWrite));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial1PathAfterNegativeWrite", observedMaterial1PathAfterNegativeWrite));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCountAfterNegativeWrite", observedMaterialCountAfterNegativeWrite));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayerAfterNegativeWrite", observedRenderLayerAfterNegativeWrite));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCountAfterClear", observedMaterialCountAfterClear));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0PathAfterClear", observedMaterial0PathAfterClear));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial3PathAfterClear", observedMaterial3PathAfterClear));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCastShadowsAfterClear", observedCastShadowsAfterClear));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedReceiveShadowsAfterClear", observedReceiveShadowsAfterClear));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayerAfterClear", observedRenderLayerAfterClear));
EXPECT_EQ(observedInitialMaterialCount, 2);
EXPECT_EQ(observedNegativeIndexPath, "");
EXPECT_EQ(observedOutOfRangePathBeforeClear, "");
EXPECT_EQ(observedMaterial0PathAfterNegativeWrite, "Materials/initial0.mat");
EXPECT_EQ(observedMaterial1PathAfterNegativeWrite, "Materials/initial1.mat");
EXPECT_EQ(observedMaterialCountAfterNegativeWrite, 2);
EXPECT_EQ(observedRenderLayerAfterNegativeWrite, 0);
EXPECT_EQ(observedMaterialCountAfterClear, 0);
EXPECT_EQ(observedMaterial0PathAfterClear, "");
EXPECT_EQ(observedMaterial3PathAfterClear, "");
EXPECT_FALSE(observedCastShadowsAfterClear);
EXPECT_TRUE(observedReceiveShadowsAfterClear);
EXPECT_EQ(observedRenderLayerAfterClear, 0);
EXPECT_EQ(meshRenderer->GetMaterialCount(), 0u);
EXPECT_EQ(meshRenderer->GetMaterialPath(0), "");
EXPECT_EQ(meshRenderer->GetMaterialPath(3), "");
EXPECT_FALSE(meshRenderer->GetCastShadows());
EXPECT_TRUE(meshRenderer->GetReceiveShadows());
EXPECT_EQ(meshRenderer->GetRenderLayer(), 0u);
}
TEST_F(MonoScriptRuntimeTest, GameObjectAddComponentApiCreatesBuiltinComponentsAndAvoidsDuplicates) {
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
GameObject* host = runtimeScene->CreateGameObject("Host");
ScriptComponent* component = AddScript(host, "Gameplay", "AddComponentProbe");
engine->OnRuntimeStart(runtimeScene);
engine->OnUpdate(0.016f);
bool initialHasCamera = true;
bool initialHasLight = true;
bool initialHasMeshFilter = true;
bool initialHasMeshRenderer = true;
bool addedTransform = false;
bool addedCamera = false;
bool addedLight = false;
bool addedMeshFilter = false;
bool addedMeshRenderer = false;
bool hasCameraAfterAdd = false;
bool hasLightAfterAdd = false;
bool hasMeshFilterAfterAdd = false;
bool hasMeshRendererAfterAdd = false;
bool cameraLookupSucceeded = false;
bool lightLookupSucceeded = false;
bool meshFilterLookupSucceeded = false;
bool meshRendererLookupSucceeded = false;
float observedCameraFieldOfView = 0.0f;
float observedLightIntensity = 0.0f;
std::string observedMeshPath;
int32_t observedMaterialCount = 0;
std::string observedMaterial0Path;
int32_t observedRenderLayer = 0;
EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasCamera", initialHasCamera));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasLight", initialHasLight));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasMeshFilter", initialHasMeshFilter));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasMeshRenderer", initialHasMeshRenderer));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedTransform", addedTransform));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedCamera", addedCamera));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedLight", addedLight));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedMeshFilter", addedMeshFilter));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedMeshRenderer", addedMeshRenderer));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasCameraAfterAdd", hasCameraAfterAdd));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasLightAfterAdd", hasLightAfterAdd));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshFilterAfterAdd", hasMeshFilterAfterAdd));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshRendererAfterAdd", hasMeshRendererAfterAdd));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "CameraLookupSucceeded", cameraLookupSucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "LightLookupSucceeded", lightLookupSucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshFilterLookupSucceeded", meshFilterLookupSucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshRendererLookupSucceeded", meshRendererLookupSucceeded));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCameraFieldOfView", observedCameraFieldOfView));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLightIntensity", observedLightIntensity));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMeshPath", observedMeshPath));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCount", observedMaterialCount));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0Path", observedMaterial0Path));
EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayer", observedRenderLayer));
EXPECT_FALSE(initialHasCamera);
EXPECT_FALSE(initialHasLight);
EXPECT_FALSE(initialHasMeshFilter);
EXPECT_FALSE(initialHasMeshRenderer);
EXPECT_TRUE(addedTransform);
EXPECT_TRUE(addedCamera);
EXPECT_TRUE(addedLight);
EXPECT_TRUE(addedMeshFilter);
EXPECT_TRUE(addedMeshRenderer);
EXPECT_TRUE(hasCameraAfterAdd);
EXPECT_TRUE(hasLightAfterAdd);
EXPECT_TRUE(hasMeshFilterAfterAdd);
EXPECT_TRUE(hasMeshRendererAfterAdd);
EXPECT_TRUE(cameraLookupSucceeded);
EXPECT_TRUE(lightLookupSucceeded);
EXPECT_TRUE(meshFilterLookupSucceeded);
EXPECT_TRUE(meshRendererLookupSucceeded);
EXPECT_FLOAT_EQ(observedCameraFieldOfView, 82.0f);
EXPECT_FLOAT_EQ(observedLightIntensity, 4.5f);
EXPECT_EQ(observedMeshPath, "Meshes/added.mesh");
EXPECT_EQ(observedMaterialCount, 1);
EXPECT_EQ(observedMaterial0Path, "Materials/added.mat");
EXPECT_EQ(observedRenderLayer, 6);
ASSERT_NE(host->GetTransform(), nullptr);
EXPECT_EQ(host->GetComponents<CameraComponent>().size(), 1u);
EXPECT_EQ(host->GetComponents<LightComponent>().size(), 1u);
EXPECT_EQ(host->GetComponents<MeshFilterComponent>().size(), 1u);
EXPECT_EQ(host->GetComponents<MeshRendererComponent>().size(), 1u);
CameraComponent* camera = host->GetComponent<CameraComponent>();
LightComponent* light = host->GetComponent<LightComponent>();
MeshFilterComponent* meshFilter = host->GetComponent<MeshFilterComponent>();
MeshRendererComponent* meshRenderer = host->GetComponent<MeshRendererComponent>();
ASSERT_NE(camera, nullptr);
ASSERT_NE(light, nullptr);
ASSERT_NE(meshFilter, nullptr);
ASSERT_NE(meshRenderer, nullptr);
EXPECT_FLOAT_EQ(camera->GetFieldOfView(), 82.0f);
EXPECT_FLOAT_EQ(light->GetIntensity(), 4.5f);
EXPECT_EQ(meshFilter->GetMeshPath(), "Meshes/added.mesh");
ASSERT_EQ(meshRenderer->GetMaterialCount(), 1u);
EXPECT_EQ(meshRenderer->GetMaterialPath(0), "Materials/added.mat");
EXPECT_EQ(meshRenderer->GetRenderLayer(), 6u);
}
TEST_F(MonoScriptRuntimeTest, TransformHierarchyApiExposesParentChildAndReparenting) {
Scene* runtimeScene = CreateScene("MonoRuntimeScene");
GameObject* root = runtimeScene->CreateGameObject("Root");