feat(engine): C++ native script system (ScriptBehavior + registry)
ScriptComponent stores only a script name string (POD, serializable); instances are reconciled by ScriptRuntime each frame (PhysicsWorld pattern): orphan/rename cleanup with OnDestroy, per-name instantiation from a file-level self-registering registry (ENGINE_REGISTER_SCRIPT), OnCreate on bind, OnUpdate after physics step on scaled dt. Engine framerate loop only sees the base interface, so future Lua/C# behaviors plug in as foreign ScriptBehavior implementations with zero engine changes. Ships a Spinner sample script and Inspector script picker.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// ScriptRuntime.cpp:脚本对账与生命周期驱动。
|
||||
//
|
||||
|
||||
#include <Engine/Scripting/ScriptRuntime.h>
|
||||
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/ECS/Components.h>
|
||||
|
||||
namespace Engine {
|
||||
namespace Scripting {
|
||||
|
||||
void ScriptRuntime::Update(f32 dt) {
|
||||
Sync();
|
||||
|
||||
auto& reg = m_Scene.GetRegistry();
|
||||
for (auto& [bits, inst] : m_Instances) {
|
||||
const auto e = static_cast<entt::entity>(bits);
|
||||
if (!reg.valid(e) || !reg.any_of<ECS::ScriptComponent>(e)) {
|
||||
continue; // 延迟销毁队列里,下帧 Sync 清理
|
||||
}
|
||||
if (!inst.created) {
|
||||
inst.behavior->OnCreate();
|
||||
inst.created = true;
|
||||
}
|
||||
inst.behavior->OnUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptRuntime::Sync() {
|
||||
auto& reg = m_Scene.GetRegistry();
|
||||
|
||||
// 1. 孤儿/改名清理:实体或组件没了、脚本名变了 → OnDestroy + 删除
|
||||
for (auto it = m_Instances.begin(); it != m_Instances.end();) {
|
||||
const auto e = static_cast<entt::entity>(it->first);
|
||||
const ECS::ScriptComponent* sc =
|
||||
reg.valid(e) ? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
|
||||
const bool stale = !sc || !it->second.behavior ||
|
||||
it->second.behavior->Name() != sc->scriptName;
|
||||
if (stale) {
|
||||
if (it->second.behavior) {
|
||||
it->second.behavior->OnDestroy();
|
||||
}
|
||||
it = m_Instances.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 缺失实例化:按名查注册表(未知名警告——场景含未注册脚本时
|
||||
// 弱前向兼容,跳过不影响其余实体)
|
||||
const auto view = reg.view<ECS::ScriptComponent>();
|
||||
for (auto [e, sc] : view.each()) {
|
||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||
if (m_Instances.contains(bits) || sc.scriptName.empty()) {
|
||||
continue;
|
||||
}
|
||||
auto behavior = ScriptRegistry::Create(sc.scriptName);
|
||||
if (!behavior) {
|
||||
ENGINE_WARN("Script '{}' not registered (entity {})",
|
||||
sc.scriptName, bits);
|
||||
continue;
|
||||
}
|
||||
behavior->Bind(m_Scene, e);
|
||||
|
||||
ScriptInstance inst;
|
||||
inst.behavior = std::move(behavior);
|
||||
inst.behavior->OnCreate(); // Unity Awake 语义:实例化即回调
|
||||
inst.created = true;
|
||||
m_Instances.emplace(bits, std::move(inst));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Scripting
|
||||
} // namespace Engine
|
||||
Reference in New Issue
Block a user