#include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace XCEngine::Components; using namespace XCEngine::Scripting; namespace { void ExpectVector3Near(const XCEngine::Math::Vector3& actual, const XCEngine::Math::Vector3& expected, float tolerance = 0.001f) { EXPECT_NEAR(actual.x, expected.x, tolerance); EXPECT_NEAR(actual.y, expected.y, tolerance); EXPECT_NEAR(actual.z, expected.z, tolerance); } MonoScriptRuntime::Settings CreateMonoSettings() { MonoScriptRuntime::Settings settings; settings.assemblyDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.corlibDirectory = XCENGINE_TEST_MANAGED_OUTPUT_DIR; settings.coreAssemblyPath = XCENGINE_TEST_SCRIPT_CORE_DLL; settings.appAssemblyPath = XCENGINE_TEST_GAME_SCRIPTS_DLL; return settings; } class MonoScriptRuntimeTest : public ::testing::Test { protected: void SetUp() override { engine = &ScriptEngine::Get(); engine->OnRuntimeStop(); runtime = std::make_unique(CreateMonoSettings()); ASSERT_TRUE(runtime->Initialize()) << runtime->GetLastError(); engine->SetRuntime(runtime.get()); } void TearDown() override { engine->OnRuntimeStop(); engine->SetRuntime(nullptr); runtime.reset(); scene.reset(); } Scene* CreateScene(const std::string& sceneName) { scene = std::make_unique(sceneName); return scene.get(); } ScriptComponent* AddScript(GameObject* gameObject, const std::string& namespaceName, const std::string& className) { ScriptComponent* component = gameObject->AddComponent(); component->SetScriptClass("GameScripts", namespaceName, className); return component; } ScriptEngine* engine = nullptr; std::unique_ptr runtime; std::unique_ptr scene; }; TEST_F(MonoScriptRuntimeTest, InitializesAndDiscoversConcreteMonoBehaviourClasses) { const std::vector classNames = runtime->GetScriptClassNames("GameScripts"); EXPECT_TRUE(runtime->IsInitialized()); EXPECT_TRUE(runtime->IsClassAvailable("GameScripts", "Gameplay", "LifecycleProbe")); EXPECT_NE(std::find(classNames.begin(), classNames.end(), "Gameplay.LifecycleProbe"), classNames.end()); EXPECT_EQ(std::find(classNames.begin(), classNames.end(), "Gameplay.AbstractLifecycleProbe"), classNames.end()); EXPECT_EQ(std::find(classNames.begin(), classNames.end(), "Gameplay.UtilityHelper"), classNames.end()); } TEST_F(MonoScriptRuntimeTest, ScriptEngineAppliesStoredFieldsAndInvokesLifecycleMethods) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); GameObject* target = runtimeScene->CreateGameObject("Target"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); component->GetFieldStorage().SetFieldValue("Speed", 5.0f); component->GetFieldStorage().SetFieldValue("Label", "Configured"); component->GetFieldStorage().SetFieldValue("Target", GameObjectReference{target->GetUUID()}); component->GetFieldStorage().SetFieldValue("SpawnPoint", XCEngine::Math::Vector3(2.0f, 4.0f, 6.0f)); engine->OnRuntimeStart(runtimeScene); engine->OnFixedUpdate(0.02f); engine->OnUpdate(0.016f); engine->OnLateUpdate(0.016f); EXPECT_TRUE(runtime->HasManagedInstance(component)); EXPECT_EQ(runtime->GetManagedInstanceCount(), 1u); int32_t awakeCount = 0; int32_t enableCount = 0; int32_t startCount = 0; int32_t fixedUpdateCount = 0; int32_t updateCount = 0; int32_t lateUpdateCount = 0; bool wasAwakened = false; bool warningLogged = false; bool errorLogged = false; bool hasTransform = false; bool transformLookupSucceeded = false; bool hasUnsupportedComponent = true; bool unsupportedComponentLookupReturnedNull = false; bool targetResolved = false; bool rotationAccessed = false; bool scaleAccessed = false; bool transformAccessed = false; bool observedEnabled = false; bool observedActiveSelf = false; bool observedActiveInHierarchy = false; bool observedIsActiveAndEnabled = false; float speed = 0.0f; float observedFixedDeltaTime = 0.0f; float observedUpdateDeltaTime = 0.0f; float observedLateDeltaTime = 0.0f; std::string label; std::string observedGameObjectName; std::string observedTargetName; GameObjectReference targetReference; GameObjectReference selfReference; XCEngine::Math::Vector4 observedLocalRotation; XCEngine::Math::Vector3 observedLocalPosition; XCEngine::Math::Vector3 observedLocalScale; XCEngine::Math::Vector3 spawnPoint; EXPECT_TRUE(runtime->TryGetFieldValue(component, "AwakeCount", awakeCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "EnableCount", enableCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "StartCount", startCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "FixedUpdateCount", fixedUpdateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LateUpdateCount", lateUpdateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "WasAwakened", wasAwakened)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "WarningLogged", warningLogged)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ErrorLogged", errorLogged)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasTransform", hasTransform)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "TransformLookupSucceeded", transformLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasUnsupportedComponent", hasUnsupportedComponent)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UnsupportedComponentLookupReturnedNull", unsupportedComponentLookupReturnedNull)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "TargetResolved", targetResolved)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "RotationAccessed", rotationAccessed)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ScaleAccessed", scaleAccessed)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "TransformAccessed", transformAccessed)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedEnabled", observedEnabled)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedActiveSelf", observedActiveSelf)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedActiveInHierarchy", observedActiveInHierarchy)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedIsActiveAndEnabled", observedIsActiveAndEnabled)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "Speed", speed)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFixedDeltaTime", observedFixedDeltaTime)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdateDeltaTime", observedUpdateDeltaTime)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLateDeltaTime", observedLateDeltaTime)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "Label", label)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedGameObjectName", observedGameObjectName)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedTargetName", observedTargetName)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "Target", targetReference)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "SelfReference", selfReference)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalRotation", observedLocalRotation)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalPosition", observedLocalPosition)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalScale", observedLocalScale)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "SpawnPoint", spawnPoint)); EXPECT_EQ(awakeCount, 1); EXPECT_EQ(enableCount, 1); EXPECT_EQ(startCount, 1); EXPECT_EQ(fixedUpdateCount, 1); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 1); EXPECT_TRUE(wasAwakened); EXPECT_TRUE(warningLogged); EXPECT_TRUE(errorLogged); EXPECT_TRUE(hasTransform); EXPECT_TRUE(transformLookupSucceeded); EXPECT_FALSE(hasUnsupportedComponent); EXPECT_TRUE(unsupportedComponentLookupReturnedNull); EXPECT_TRUE(targetResolved); EXPECT_TRUE(rotationAccessed); EXPECT_TRUE(scaleAccessed); EXPECT_TRUE(transformAccessed); EXPECT_TRUE(observedEnabled); EXPECT_TRUE(observedActiveSelf); EXPECT_TRUE(observedActiveInHierarchy); EXPECT_TRUE(observedIsActiveAndEnabled); EXPECT_FLOAT_EQ(speed, 6.0f); EXPECT_FLOAT_EQ(observedFixedDeltaTime, 0.02f); EXPECT_FLOAT_EQ(observedUpdateDeltaTime, 0.016f); EXPECT_FLOAT_EQ(observedLateDeltaTime, 0.016f); EXPECT_EQ(label, "Configured|Awake"); EXPECT_EQ(observedGameObjectName, "Host_Managed"); EXPECT_EQ(observedTargetName, "Target"); EXPECT_EQ(targetReference, GameObjectReference{target->GetUUID()}); EXPECT_EQ(selfReference, GameObjectReference{host->GetUUID()}); EXPECT_EQ(observedLocalRotation, XCEngine::Math::Vector4(0.0f, 0.5f, 0.0f, 0.8660254f)); EXPECT_EQ(observedLocalPosition, XCEngine::Math::Vector3(8.0f, 8.0f, 9.0f)); EXPECT_EQ(observedLocalScale, XCEngine::Math::Vector3(3.0f, 3.0f, 4.0f)); EXPECT_EQ(spawnPoint, XCEngine::Math::Vector3(3.0f, 4.0f, 6.0f)); EXPECT_EQ(host->GetName(), "Host_Managed"); EXPECT_EQ(host->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(8.0f, 8.0f, 9.0f)); EXPECT_EQ(host->GetTransform()->GetLocalScale(), XCEngine::Math::Vector3(3.0f, 3.0f, 4.0f)); const XCEngine::Math::Quaternion& localRotation = host->GetTransform()->GetLocalRotation(); EXPECT_FLOAT_EQ(localRotation.x, 0.0f); EXPECT_FLOAT_EQ(localRotation.y, 0.5f); EXPECT_FLOAT_EQ(localRotation.z, 0.0f); EXPECT_FLOAT_EQ(localRotation.w, 0.8660254f); } TEST_F(MonoScriptRuntimeTest, GameObjectComponentApiResolvesTransformAndRejectsUnsupportedManagedTypes) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); bool hasTransform = false; bool transformLookupSucceeded = false; bool hasUnsupportedComponent = true; bool unsupportedComponentLookupReturnedNull = false; EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasTransform", hasTransform)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "TransformLookupSucceeded", transformLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasUnsupportedComponent", hasUnsupportedComponent)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UnsupportedComponentLookupReturnedNull", unsupportedComponentLookupReturnedNull)); EXPECT_TRUE(hasTransform); EXPECT_TRUE(transformLookupSucceeded); EXPECT_FALSE(hasUnsupportedComponent); EXPECT_TRUE(unsupportedComponentLookupReturnedNull); EXPECT_EQ(host->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(7.0f, 8.0f, 9.0f)); } TEST_F(MonoScriptRuntimeTest, ManagedBuiltInComponentWrappersReadAndWriteCameraAndLight) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); CameraComponent* camera = host->AddComponent(); LightComponent* light = host->AddComponent(); ScriptComponent* component = AddScript(host, "Gameplay", "BuiltinComponentProbe"); camera->SetFieldOfView(52.0f); camera->SetNearClipPlane(0.2f); camera->SetFarClipPlane(300.0f); camera->SetDepth(1.5f); camera->SetPrimary(true); light->SetIntensity(1.25f); light->SetRange(12.0f); light->SetSpotAngle(33.0f); light->SetCastsShadows(false); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); bool hasCamera = false; bool hasLight = false; bool cameraLookupSucceeded = false; bool lightLookupSucceeded = false; bool observedPrimary = false; bool observedCastsShadows = true; float observedFieldOfView = 0.0f; float observedNearClipPlane = 0.0f; float observedFarClipPlane = 0.0f; float observedDepth = 0.0f; float observedIntensity = 0.0f; float observedRange = 0.0f; float observedSpotAngle = 0.0f; EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasCamera", hasCamera)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasLight", hasLight)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "CameraLookupSucceeded", cameraLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LightLookupSucceeded", lightLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFieldOfView", observedFieldOfView)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedNearClipPlane", observedNearClipPlane)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFarClipPlane", observedFarClipPlane)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedDepth", observedDepth)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedPrimary", observedPrimary)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedIntensity", observedIntensity)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRange", observedRange)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedSpotAngle", observedSpotAngle)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCastsShadows", observedCastsShadows)); EXPECT_TRUE(hasCamera); EXPECT_TRUE(hasLight); EXPECT_TRUE(cameraLookupSucceeded); EXPECT_TRUE(lightLookupSucceeded); EXPECT_FLOAT_EQ(observedFieldOfView, 52.0f); EXPECT_FLOAT_EQ(observedNearClipPlane, 0.2f); EXPECT_FLOAT_EQ(observedFarClipPlane, 300.0f); EXPECT_FLOAT_EQ(observedDepth, 1.5f); EXPECT_TRUE(observedPrimary); EXPECT_FLOAT_EQ(observedIntensity, 1.25f); EXPECT_FLOAT_EQ(observedRange, 12.0f); EXPECT_FLOAT_EQ(observedSpotAngle, 33.0f); EXPECT_FALSE(observedCastsShadows); EXPECT_FLOAT_EQ(camera->GetFieldOfView(), 75.0f); EXPECT_FLOAT_EQ(camera->GetNearClipPlane(), 0.3f); EXPECT_FLOAT_EQ(camera->GetFarClipPlane(), 500.0f); EXPECT_FLOAT_EQ(camera->GetDepth(), 3.0f); EXPECT_FALSE(camera->IsPrimary()); EXPECT_FLOAT_EQ(light->GetIntensity(), 2.5f); EXPECT_FLOAT_EQ(light->GetRange(), 42.0f); EXPECT_FLOAT_EQ(light->GetSpotAngle(), 55.0f); EXPECT_TRUE(light->GetCastsShadows()); } TEST_F(MonoScriptRuntimeTest, TransformHierarchyApiExposesParentChildAndReparenting) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* root = runtimeScene->CreateGameObject("Root"); GameObject* host = runtimeScene->CreateGameObject("Host", root); GameObject* child = runtimeScene->CreateGameObject("Child", host); GameObject* reparentTarget = runtimeScene->CreateGameObject("ReparentTarget"); ScriptComponent* component = AddScript(host, "Gameplay", "HierarchyProbe"); component->GetFieldStorage().SetFieldValue("ReparentTarget", GameObjectReference{reparentTarget->GetUUID()}); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); int32_t observedChildCount = 0; bool parentLookupSucceeded = false; bool childLookupSucceeded = false; std::string observedParentName; std::string observedFirstChildName; std::string reparentedChildParentName; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedChildCount", observedChildCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ParentLookupSucceeded", parentLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ChildLookupSucceeded", childLookupSucceeded)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedParentName", observedParentName)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedFirstChildName", observedFirstChildName)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ReparentedChildParentName", reparentedChildParentName)); EXPECT_TRUE(parentLookupSucceeded); EXPECT_TRUE(childLookupSucceeded); EXPECT_EQ(observedChildCount, 1); EXPECT_EQ(observedParentName, "Root"); EXPECT_EQ(observedFirstChildName, "Child"); EXPECT_EQ(reparentedChildParentName, "ReparentTarget"); EXPECT_EQ(host->GetParent(), root); EXPECT_EQ(host->GetChildCount(), 0u); EXPECT_EQ(child->GetParent(), reparentTarget); EXPECT_EQ(reparentTarget->GetChildCount(), 1u); } TEST_F(MonoScriptRuntimeTest, TransformSpaceApiReadsAndWritesWorldAndLocalValues) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* parent = runtimeScene->CreateGameObject("Parent"); GameObject* host = runtimeScene->CreateGameObject("Host", parent); ScriptComponent* component = AddScript(host, "Gameplay", "TransformSpaceProbe"); parent->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(10.0f, 0.0f, 0.0f)); parent->GetTransform()->SetLocalScale(XCEngine::Math::Vector3(2.0f, 2.0f, 2.0f)); host->GetTransform()->SetLocalPosition(XCEngine::Math::Vector3(1.0f, 2.0f, 3.0f)); host->GetTransform()->SetLocalScale(XCEngine::Math::Vector3(1.0f, 1.0f, 1.0f)); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); XCEngine::Math::Vector3 observedInitialWorldPosition; XCEngine::Math::Vector3 observedInitialWorldScale; XCEngine::Math::Vector3 observedInitialLocalEulerAngles; XCEngine::Math::Vector3 observedEulerAfterSet; XCEngine::Math::Vector3 observedWorldPositionAfterSet; XCEngine::Math::Vector3 observedWorldScaleAfterSet; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialWorldPosition", observedInitialWorldPosition)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialWorldScale", observedInitialWorldScale)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialLocalEulerAngles", observedInitialLocalEulerAngles)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedEulerAfterSet", observedEulerAfterSet)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedWorldPositionAfterSet", observedWorldPositionAfterSet)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedWorldScaleAfterSet", observedWorldScaleAfterSet)); EXPECT_EQ(observedInitialWorldPosition, XCEngine::Math::Vector3(12.0f, 4.0f, 6.0f)); EXPECT_EQ(observedInitialWorldScale, XCEngine::Math::Vector3(2.0f, 2.0f, 2.0f)); EXPECT_EQ(observedInitialLocalEulerAngles, XCEngine::Math::Vector3(0.0f, 0.0f, 0.0f)); EXPECT_NEAR(observedEulerAfterSet.x, 0.0f, 0.001f); EXPECT_NEAR(observedEulerAfterSet.y, 45.0f, 0.001f); EXPECT_NEAR(observedEulerAfterSet.z, 0.0f, 0.001f); EXPECT_EQ(observedWorldPositionAfterSet, XCEngine::Math::Vector3(20.0f, 6.0f, 10.0f)); EXPECT_EQ(observedWorldScaleAfterSet, XCEngine::Math::Vector3(6.0f, 8.0f, 10.0f)); EXPECT_EQ(host->GetTransform()->GetLocalPosition(), XCEngine::Math::Vector3(5.0f, 3.0f, 5.0f)); EXPECT_EQ(host->GetTransform()->GetPosition(), XCEngine::Math::Vector3(20.0f, 6.0f, 10.0f)); EXPECT_EQ(host->GetTransform()->GetLocalScale(), XCEngine::Math::Vector3(3.0f, 4.0f, 5.0f)); EXPECT_EQ(host->GetTransform()->GetScale(), XCEngine::Math::Vector3(6.0f, 8.0f, 10.0f)); const XCEngine::Math::Quaternion& localRotation = host->GetTransform()->GetLocalRotation(); EXPECT_FLOAT_EQ(localRotation.x, 0.0f); EXPECT_FLOAT_EQ(localRotation.y, 0.5f); EXPECT_FLOAT_EQ(localRotation.z, 0.0f); EXPECT_FLOAT_EQ(localRotation.w, 0.8660254f); } TEST_F(MonoScriptRuntimeTest, TransformMotionApiExposesDirectionVectorsAndAppliesOperations) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "TransformMotionProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); XCEngine::Math::Vector3 observedForward; XCEngine::Math::Vector3 observedRight; XCEngine::Math::Vector3 observedUp; XCEngine::Math::Vector3 observedPositionAfterSelfTranslate; XCEngine::Math::Vector3 observedPositionAfterWorldTranslate; XCEngine::Math::Vector3 observedForwardAfterWorldRotate; XCEngine::Math::Vector3 observedForwardAfterSelfRotate; XCEngine::Math::Vector3 observedForwardAfterLookAt; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForward", observedForward)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRight", observedRight)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUp", observedUp)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedPositionAfterSelfTranslate", observedPositionAfterSelfTranslate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedPositionAfterWorldTranslate", observedPositionAfterWorldTranslate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterWorldRotate", observedForwardAfterWorldRotate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterSelfRotate", observedForwardAfterSelfRotate)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedForwardAfterLookAt", observedForwardAfterLookAt)); ExpectVector3Near(observedForward, XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)); ExpectVector3Near(observedRight, XCEngine::Math::Vector3(0.0f, 0.0f, -1.0f)); ExpectVector3Near(observedUp, XCEngine::Math::Vector3(0.0f, 1.0f, 0.0f)); ExpectVector3Near(observedPositionAfterSelfTranslate, XCEngine::Math::Vector3(2.0f, 0.0f, 0.0f)); ExpectVector3Near(observedPositionAfterWorldTranslate, XCEngine::Math::Vector3(2.0f, 0.0f, 3.0f)); ExpectVector3Near(observedForwardAfterWorldRotate, XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); ExpectVector3Near(observedForwardAfterSelfRotate, XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)); ExpectVector3Near(observedForwardAfterLookAt, XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); ExpectVector3Near(host->GetTransform()->GetPosition(), XCEngine::Math::Vector3(2.0f, 0.0f, 3.0f)); ExpectVector3Near(host->GetTransform()->GetForward(), XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); } TEST_F(MonoScriptRuntimeTest, TransformConversionApiTransformsPointsAndDirectionsBetweenSpaces) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "TransformConversionProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); XCEngine::Math::Vector3 observedWorldPoint; XCEngine::Math::Vector3 observedLocalPoint; XCEngine::Math::Vector3 observedWorldDirection; XCEngine::Math::Vector3 observedLocalDirection; EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedWorldPoint", observedWorldPoint)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalPoint", observedLocalPoint)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedWorldDirection", observedWorldDirection)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLocalDirection", observedLocalDirection)); ExpectVector3Near(observedWorldPoint, XCEngine::Math::Vector3(7.0f, 0.0f, 1.0f)); ExpectVector3Near(observedLocalPoint, XCEngine::Math::Vector3(0.0f, 0.0f, 2.0f)); ExpectVector3Near(observedWorldDirection, XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)); ExpectVector3Near(observedLocalDirection, XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); ExpectVector3Near(host->GetTransform()->TransformPoint(XCEngine::Math::Vector3(0.0f, 0.0f, 2.0f)), XCEngine::Math::Vector3(7.0f, 0.0f, 1.0f)); ExpectVector3Near(host->GetTransform()->InverseTransformDirection(XCEngine::Math::Vector3(1.0f, 0.0f, 0.0f)), XCEngine::Math::Vector3(0.0f, 0.0f, 1.0f)); } TEST_F(MonoScriptRuntimeTest, ManagedBehaviourCanDisableItselfThroughEnabledProperty) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); component->GetFieldStorage().SetFieldValue("DisableSelfOnFirstUpdate", true); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); engine->OnLateUpdate(0.016f); int32_t updateCount = 0; int32_t lateUpdateCount = 0; int32_t disableCount = 0; bool observedEnabled = false; bool observedIsActiveAndEnabled = false; EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LateUpdateCount", lateUpdateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "DisableCount", disableCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedEnabled", observedEnabled)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedIsActiveAndEnabled", observedIsActiveAndEnabled)); EXPECT_FALSE(component->IsEnabled()); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 0); EXPECT_EQ(disableCount, 1); EXPECT_TRUE(observedEnabled); EXPECT_TRUE(observedIsActiveAndEnabled); engine->OnUpdate(0.016f); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_EQ(updateCount, 1); } TEST_F(MonoScriptRuntimeTest, ManagedScriptCanDeactivateItsHostGameObject) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); component->GetFieldStorage().SetFieldValue("DeactivateGameObjectOnFirstUpdate", true); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); engine->OnLateUpdate(0.016f); int32_t updateCount = 0; int32_t lateUpdateCount = 0; int32_t disableCount = 0; bool observedActiveSelf = false; bool observedActiveInHierarchy = false; EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "LateUpdateCount", lateUpdateCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "DisableCount", disableCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedActiveSelf", observedActiveSelf)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedActiveInHierarchy", observedActiveInHierarchy)); EXPECT_FALSE(host->IsActive()); EXPECT_FALSE(host->IsActiveInHierarchy()); EXPECT_TRUE(component->IsEnabled()); EXPECT_EQ(updateCount, 1); EXPECT_EQ(lateUpdateCount, 0); EXPECT_EQ(disableCount, 1); EXPECT_TRUE(observedActiveSelf); EXPECT_TRUE(observedActiveInHierarchy); engine->OnUpdate(0.016f); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_EQ(updateCount, 1); } TEST_F(MonoScriptRuntimeTest, DisableEnablePreservesSingleInstanceAndStartOnlyRunsOnce) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* host = runtimeScene->CreateGameObject("Host"); ScriptComponent* component = AddScript(host, "Gameplay", "LifecycleProbe"); engine->OnRuntimeStart(runtimeScene); engine->OnUpdate(0.016f); component->SetEnabled(false); int32_t disableCount = 0; EXPECT_TRUE(runtime->TryGetFieldValue(component, "DisableCount", disableCount)); EXPECT_EQ(disableCount, 1); EXPECT_EQ(runtime->GetManagedInstanceCount(), 1u); component->SetEnabled(true); engine->OnUpdate(0.016f); int32_t enableCount = 0; int32_t startCount = 0; int32_t updateCount = 0; EXPECT_TRUE(runtime->TryGetFieldValue(component, "EnableCount", enableCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "StartCount", startCount)); EXPECT_TRUE(runtime->TryGetFieldValue(component, "UpdateCount", updateCount)); EXPECT_EQ(enableCount, 2); EXPECT_EQ(startCount, 1); EXPECT_EQ(updateCount, 2); engine->OnRuntimeStop(); EXPECT_FALSE(runtime->HasManagedInstance(component)); EXPECT_EQ(runtime->GetManagedInstanceCount(), 0u); } } // namespace