feat(physics): dispatch PhysX simulation events to scene scripts
This commit is contained in:
@@ -43,6 +43,19 @@ std::string LifecycleMethodToString(ScriptLifecycleMethod method) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string PhysicsMessageToString(ScriptPhysicsMessage message) {
|
||||
switch (message) {
|
||||
case ScriptPhysicsMessage::CollisionEnter: return "CollisionEnter";
|
||||
case ScriptPhysicsMessage::CollisionStay: return "CollisionStay";
|
||||
case ScriptPhysicsMessage::CollisionExit: return "CollisionExit";
|
||||
case ScriptPhysicsMessage::TriggerEnter: return "TriggerEnter";
|
||||
case ScriptPhysicsMessage::TriggerStay: return "TriggerStay";
|
||||
case ScriptPhysicsMessage::TriggerExit: return "TriggerExit";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
class OrderedObserverComponent : public Component {
|
||||
public:
|
||||
explicit OrderedObserverComponent(std::vector<std::string>* events)
|
||||
@@ -81,6 +94,38 @@ private:
|
||||
std::vector<std::string>* m_events = nullptr;
|
||||
};
|
||||
|
||||
class PhysicsObserverComponent : public Component {
|
||||
public:
|
||||
explicit PhysicsObserverComponent(std::vector<std::string>* events)
|
||||
: m_events(events) {
|
||||
}
|
||||
|
||||
std::string GetName() const override { return "PhysicsObserver"; }
|
||||
|
||||
void OnCollisionEnter(GameObject* other) override { Record("NativeCollisionEnter", other); }
|
||||
void OnCollisionStay(GameObject* other) override { Record("NativeCollisionStay", other); }
|
||||
void OnCollisionExit(GameObject* other) override { Record("NativeCollisionExit", other); }
|
||||
void OnTriggerEnter(GameObject* other) override { Record("NativeTriggerEnter", other); }
|
||||
void OnTriggerStay(GameObject* other) override { Record("NativeTriggerStay", other); }
|
||||
void OnTriggerExit(GameObject* other) override { Record("NativeTriggerExit", other); }
|
||||
|
||||
private:
|
||||
void Record(const char* prefix, GameObject* other) {
|
||||
if (!m_events || !GetGameObject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_events->push_back(
|
||||
std::string(prefix)
|
||||
+ ":"
|
||||
+ GetGameObject()->GetName()
|
||||
+ ":"
|
||||
+ (other ? other->GetName() : std::string("null")));
|
||||
}
|
||||
|
||||
std::vector<std::string>* m_events = nullptr;
|
||||
};
|
||||
|
||||
class TempFileScope {
|
||||
public:
|
||||
TempFileScope(std::string stem, std::string extension, std::string contents) {
|
||||
@@ -256,6 +301,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void InvokePhysicsMessage(
|
||||
const ScriptRuntimeContext& context,
|
||||
ScriptPhysicsMessage message,
|
||||
GameObject* other) override {
|
||||
if (m_events) {
|
||||
m_events->push_back(
|
||||
PhysicsMessageToString(message)
|
||||
+ ":"
|
||||
+ Describe(context)
|
||||
+ ":"
|
||||
+ (other ? other->GetName() : std::string("null")));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static std::string Describe(const ScriptRuntimeContext& context) {
|
||||
const std::string gameObjectName = context.gameObject ? context.gameObject->GetName() : "null";
|
||||
@@ -427,6 +486,101 @@ TEST_F(SceneRuntimeTest, FrameOrderRunsScriptLifecycleBeforeNativeComponents) {
|
||||
EXPECT_EQ(events, expected);
|
||||
}
|
||||
|
||||
TEST_F(SceneRuntimeTest, FixedUpdateDispatchesTriggerPhysicsMessagesToNativeAndScripts) {
|
||||
if (!XCEngine::Physics::PhysicsWorld::IsPhysXAvailable()) {
|
||||
GTEST_SKIP() << "PhysX is not available in this configuration.";
|
||||
}
|
||||
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* mover = runtimeScene->CreateGameObject("Mover");
|
||||
mover->AddComponent<PhysicsObserverComponent>(&events);
|
||||
mover->AddComponent<SphereColliderComponent>();
|
||||
RigidbodyComponent* rigidbody = mover->AddComponent<RigidbodyComponent>();
|
||||
rigidbody->SetUseGravity(false);
|
||||
AddScript(mover, "Gameplay", "TriggerProbe");
|
||||
mover->GetTransform()->SetPosition(XCEngine::Math::Vector3(-3.0f, 0.0f, 0.0f));
|
||||
|
||||
GameObject* triggerZone = runtimeScene->CreateGameObject("TriggerZone");
|
||||
BoxColliderComponent* triggerCollider = triggerZone->AddComponent<BoxColliderComponent>();
|
||||
triggerCollider->SetSize(XCEngine::Math::Vector3(2.0f, 2.0f, 2.0f));
|
||||
triggerCollider->SetTrigger(true);
|
||||
|
||||
runtime.Start(runtimeScene);
|
||||
events.clear();
|
||||
|
||||
rigidbody->SetLinearVelocity(XCEngine::Math::Vector3(150.0f, 0.0f, 0.0f));
|
||||
runtime.FixedUpdate(0.02f);
|
||||
rigidbody->SetLinearVelocity(XCEngine::Math::Vector3::Zero());
|
||||
runtime.FixedUpdate(0.02f);
|
||||
rigidbody->SetLinearVelocity(XCEngine::Math::Vector3(150.0f, 0.0f, 0.0f));
|
||||
runtime.FixedUpdate(0.02f);
|
||||
rigidbody->SetLinearVelocity(XCEngine::Math::Vector3::Zero());
|
||||
runtime.FixedUpdate(0.02f);
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"FixedUpdate:Mover:Gameplay.TriggerProbe",
|
||||
"FixedUpdate:Mover:Gameplay.TriggerProbe",
|
||||
"NativeTriggerEnter:Mover:TriggerZone",
|
||||
"TriggerEnter:Mover:Gameplay.TriggerProbe:TriggerZone",
|
||||
"NativeTriggerStay:Mover:TriggerZone",
|
||||
"TriggerStay:Mover:Gameplay.TriggerProbe:TriggerZone",
|
||||
"FixedUpdate:Mover:Gameplay.TriggerProbe",
|
||||
"NativeTriggerStay:Mover:TriggerZone",
|
||||
"TriggerStay:Mover:Gameplay.TriggerProbe:TriggerZone",
|
||||
"FixedUpdate:Mover:Gameplay.TriggerProbe",
|
||||
"NativeTriggerExit:Mover:TriggerZone",
|
||||
"TriggerExit:Mover:Gameplay.TriggerProbe:TriggerZone"
|
||||
};
|
||||
EXPECT_EQ(events, expected);
|
||||
}
|
||||
|
||||
TEST_F(SceneRuntimeTest, FixedUpdateDispatchesCollisionPhysicsMessagesToNativeAndScripts) {
|
||||
if (!XCEngine::Physics::PhysicsWorld::IsPhysXAvailable()) {
|
||||
GTEST_SKIP() << "PhysX is not available in this configuration.";
|
||||
}
|
||||
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* mover = runtimeScene->CreateGameObject("Mover");
|
||||
mover->AddComponent<PhysicsObserverComponent>(&events);
|
||||
mover->AddComponent<SphereColliderComponent>();
|
||||
RigidbodyComponent* rigidbody = mover->AddComponent<RigidbodyComponent>();
|
||||
rigidbody->SetUseGravity(false);
|
||||
AddScript(mover, "Gameplay", "CollisionProbe");
|
||||
mover->GetTransform()->SetPosition(XCEngine::Math::Vector3(-3.0f, 0.0f, 0.0f));
|
||||
|
||||
GameObject* wall = runtimeScene->CreateGameObject("Wall");
|
||||
BoxColliderComponent* wallCollider = wall->AddComponent<BoxColliderComponent>();
|
||||
wallCollider->SetSize(XCEngine::Math::Vector3(2.0f, 2.0f, 2.0f));
|
||||
|
||||
runtime.Start(runtimeScene);
|
||||
events.clear();
|
||||
|
||||
rigidbody->SetLinearVelocity(XCEngine::Math::Vector3(150.0f, 0.0f, 0.0f));
|
||||
runtime.FixedUpdate(0.02f);
|
||||
rigidbody->SetLinearVelocity(XCEngine::Math::Vector3::Zero());
|
||||
runtime.FixedUpdate(0.02f);
|
||||
rigidbody->SetLinearVelocity(XCEngine::Math::Vector3(-150.0f, 0.0f, 0.0f));
|
||||
runtime.FixedUpdate(0.02f);
|
||||
rigidbody->SetLinearVelocity(XCEngine::Math::Vector3::Zero());
|
||||
runtime.FixedUpdate(0.02f);
|
||||
|
||||
const std::vector<std::string> expected = {
|
||||
"FixedUpdate:Mover:Gameplay.CollisionProbe",
|
||||
"FixedUpdate:Mover:Gameplay.CollisionProbe",
|
||||
"NativeCollisionEnter:Mover:Wall",
|
||||
"CollisionEnter:Mover:Gameplay.CollisionProbe:Wall",
|
||||
"NativeCollisionStay:Mover:Wall",
|
||||
"CollisionStay:Mover:Gameplay.CollisionProbe:Wall",
|
||||
"FixedUpdate:Mover:Gameplay.CollisionProbe",
|
||||
"NativeCollisionStay:Mover:Wall",
|
||||
"CollisionStay:Mover:Gameplay.CollisionProbe:Wall",
|
||||
"FixedUpdate:Mover:Gameplay.CollisionProbe",
|
||||
"NativeCollisionExit:Mover:Wall",
|
||||
"CollisionExit:Mover:Gameplay.CollisionProbe:Wall"
|
||||
};
|
||||
EXPECT_EQ(events, expected);
|
||||
}
|
||||
|
||||
TEST_F(SceneRuntimeTest, InactiveSceneSkipsFrameExecution) {
|
||||
Scene* runtimeScene = CreateScene("RuntimeScene");
|
||||
GameObject* host = runtimeScene->CreateGameObject("Host");
|
||||
|
||||
Reference in New Issue
Block a user