feat(physics): dispatch PhysX simulation events to scene scripts

This commit is contained in:
2026-04-15 13:36:39 +08:00
parent 077786e4c7
commit 51aea47c83
20 changed files with 955 additions and 7 deletions

View File

@@ -32,6 +32,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 FakeScriptRuntime : public IScriptRuntime {
public:
void OnRuntimeStart(Scene* scene) override {
@@ -118,6 +131,18 @@ public:
events.push_back(LifecycleMethodToString(method) + ":" + Describe(context));
}
void InvokePhysicsMessage(
const ScriptRuntimeContext& context,
ScriptPhysicsMessage message,
GameObject* other) override {
events.push_back(
PhysicsMessageToString(message)
+ ":"
+ Describe(context)
+ ":"
+ (other ? other->GetName() : std::string("null")));
}
void Clear() {
events.clear();
}
@@ -388,6 +413,50 @@ TEST_F(ScriptEngineTest, ChangingScriptClassWhileRuntimeRunningRecreatesTrackedI
EXPECT_TRUE(engine->HasRuntimeInstance(component));
}
TEST_F(ScriptEngineTest, DispatchPhysicsMessageInvokesTrackedActiveScripts) {
Scene* runtimeScene = CreateScene("RuntimeScene");
GameObject* host = runtimeScene->CreateGameObject("Host");
GameObject* other = runtimeScene->CreateGameObject("Other");
AddScriptComponent(host, "Gameplay", "TriggerListener");
engine->OnRuntimeStart(runtimeScene);
runtime.Clear();
engine->DispatchPhysicsMessage(host, ScriptPhysicsMessage::TriggerEnter, other);
const std::vector<std::string> expected = {
"TriggerEnter:Host:Gameplay.TriggerListener:Other"
};
EXPECT_EQ(runtime.events, expected);
}
TEST_F(ScriptEngineTest, DispatchPhysicsMessageSkipsInactiveAndDisabledScripts) {
Scene* runtimeScene = CreateScene("RuntimeScene");
GameObject* activeHost = runtimeScene->CreateGameObject("ActiveHost");
GameObject* inactiveHost = runtimeScene->CreateGameObject("InactiveHost");
GameObject* disabledHost = runtimeScene->CreateGameObject("DisabledHost");
GameObject* other = runtimeScene->CreateGameObject("Other");
AddScriptComponent(activeHost, "Gameplay", "ActiveListener");
AddScriptComponent(inactiveHost, "Gameplay", "InactiveListener");
ScriptComponent* disabledComponent = AddScriptComponent(disabledHost, "Gameplay", "DisabledListener");
inactiveHost->SetActive(false);
disabledComponent->SetEnabled(false);
engine->OnRuntimeStart(runtimeScene);
runtime.Clear();
engine->DispatchPhysicsMessage(activeHost, ScriptPhysicsMessage::CollisionEnter, other);
engine->DispatchPhysicsMessage(inactiveHost, ScriptPhysicsMessage::CollisionEnter, other);
engine->DispatchPhysicsMessage(disabledHost, ScriptPhysicsMessage::CollisionEnter, other);
const std::vector<std::string> expected = {
"CollisionEnter:ActiveHost:Gameplay.ActiveListener:Other"
};
EXPECT_EQ(runtime.events, expected);
}
TEST_F(ScriptEngineTest, ClearingScriptClassWhileRuntimeRunningDestroysTrackedInstance) {
Scene* runtimeScene = CreateScene("RuntimeScene");
GameObject* host = runtimeScene->CreateGameObject("Host");