// // ScriptRuntime.cpp:脚本对账与生命周期驱动(多脚本挂载)。 // #include #include #include #include #include #include #include #include #include namespace Engine { namespace Scripting { // 宿主门面访问器:Application 完整类型只在脚本 cpp 里需要, // 这里仅回引成员(避免引擎头循环依赖) Application& ScriptBehavior::App() const { return *m_App; } // ---- 场景实体 spawn/destroy 底座(声明在 Script.h,实现下沉 // 此处——Application 完整类型只在 cpp 可见)---- ECS::EntityHandle ScriptBehavior::Spawn(const std::string& name) { return App().GetScene().CreateEntity(name); } void ScriptBehavior::Destroy(ECS::Entity e) { App().GetScene().DestroyEntity(e); } // Prefab 实例化(声明在 Script.h,实现下沉此处——同 Spawn/ // Destroy 模式;序列化器与 Application 门面都在引擎内可达) ECS::Entity ScriptBehavior::Instantiate(const std::string& prefabPath, const glm::vec3& pos, const glm::quat& rot) { return Serialization::SceneSerializer::InstantiatePrefab( App().GetScene(), App().GetAssets(), prefabPath, &pos, &rot); } // ---- 参数按名读写(ScriptBehavior 声明,经绑定表;写穿 // Item.params 由运行时 SetParam 完成)---- bool ScriptBehavior::WriteParam(const std::string& name, const ParamValue& v) { for (const auto& b : m_ParamBinds) { if (b.name == name) { return ParamBuilder::WriteBind(b, v); } } return false; } bool ScriptBehavior::ReadParam(const std::string& name, ParamValue& out) const { for (const auto& b : m_ParamBinds) { if (b.name == name) { return ParamBuilder::ReadBind(b, out); } } return false; } // ---- 碰撞事件派发(Update 开头、Sync 之前):PhysicsWorld 最近 // 一次步进收集的事件转发到涉事实例。时序保证:Step 先于 // Update(帧循环序)→ 此处读到的是本帧物理结果;回调里 // spawn/destroy 安全——destroy 入延迟队列(帧尾 flush), // spawn 不改 m_Instances(下一帧 Sync 对账建实例)。禁用 // 实例不收(Unity enabled 语义对齐);Persisted 每步都来, // 直接跳过(Enter/Exit 已够 gameplay 用) void ScriptRuntime::DispatchCollisions() { if (!m_App) { return; } const std::vector& events = m_App->GetPhysics().GetEvents(); for (const auto& ev : events) { if (ev.kind == CollisionEvent::Kind::Persisted) { continue; } const bool enter = ev.kind == CollisionEvent::Kind::Added; for (const u32 self : {ev.a, ev.b}) { const u32 other = (self == ev.a) ? ev.b : ev.a; const auto it = m_Instances.find(self); if (it == m_Instances.end()) { continue; } for (auto& inst : it->second) { if (!inst.behavior || !inst.enabled) { continue; } if (enter) { inst.behavior->OnCollisionEnter( other, ev.point, ev.normal); } else { inst.behavior->OnCollisionExit(other); } } } } } void ScriptRuntime::Update(f32 dt) { DispatchCollisions(); Sync(); auto& reg = m_Scene.GetRegistry(); for (auto& [bits, list] : m_Instances) { const auto e = static_cast(bits); const ECS::ScriptComponent* sc = reg.valid(e) ? reg.try_get(e) : nullptr; for (auto& inst : list) { if (!inst.behavior) { continue; } // 启停翻转检测:wantOn = 组件总开关 && 项开关(组件 // 关闭时零查找;项按名查找沿用每帧既有的那一次)。 // 与上帧 inst.enabled 比较出沿:true→OnEnable, // false→OnDisable(Unity enabled 翻转语义) bool wantOn = false; if (sc && sc->active) { const auto item = std::find_if( sc->items.begin(), sc->items.end(), [&](const ECS::ScriptComponent::Item& it) { return it.name == inst.behavior->Name(); }); wantOn = item != sc->items.end() && item->active; } if (!wantOn) { if (inst.enabled) { inst.enabled = false; inst.behavior->OnDisable(); } continue; // 禁用 = 实例保留停 OnUpdate } if (!inst.created) { inst.behavior->OnCreate(); inst.created = true; } if (!inst.enabled) { inst.enabled = true; inst.behavior->OnEnable(); } if (!inst.started) { inst.started = true; inst.behavior->OnStart(); // 首次 OnUpdate 前一次 } inst.behavior->OnUpdate(dt); } } } // ---- 终局(GameFlow →Stopped 广播):全实例 OnDisable → // OnDestroy。与 Sync 的孤儿清理同序,范围 = 全部且不重建; // Application 的监听先于编辑器回滚注册(组合根构造序), // OnDestroy 里读到的仍是上局实体 ---- void ScriptRuntime::EndPlay() { for (auto& [bits, list] : m_Instances) { for (auto& inst : list) { if (!inst.behavior) { continue; } if (inst.enabled) { inst.enabled = false; inst.behavior->OnDisable(); } inst.behavior->OnDestroy(); } } m_Instances.clear(); } void ScriptRuntime::Sync() { auto& reg = m_Scene.GetRegistry(); // 1. 孤儿/改名清理:实体或组件没了 → 整组 OnDestroy; // 实例名不在 items 里(改名/移除)→ 单个 OnDestroy。 // 销毁次序 = OnDisable(守卫防双发)→ OnDestroy for (auto it = m_Instances.begin(); it != m_Instances.end();) { const auto e = static_cast(it->first); const ECS::ScriptComponent* sc = reg.valid(e) ? reg.try_get(e) : nullptr; if (!sc) { for (auto& inst : it->second) { if (!inst.behavior) { continue; } if (inst.enabled) { inst.enabled = false; inst.behavior->OnDisable(); } inst.behavior->OnDestroy(); } it = m_Instances.erase(it); continue; } auto& list = it->second; for (auto li = list.begin(); li != list.end();) { const bool keep = li->behavior && std::any_of(sc->items.begin(), sc->items.end(), [&](const ECS::ScriptComponent::Item& item) { return item.name == li->behavior->Name(); }); if (keep) { ++li; continue; } if (li->behavior) { if (li->enabled) { li->enabled = false; li->behavior->OnDisable(); } li->behavior->OnDestroy(); } li = list.erase(li); } if (list.empty()) { it = m_Instances.erase(it); } else { ++it; } } // 2. 缺失实例化:逐项按名查注册表(未知名警告——场景含未注册 // 脚本时弱前向兼容,跳过不影响其余脚本)。灌值次序对齐 // Unity:声明(成员=声明默认)→ 组件存档覆盖 → 收值回调 // → OnCreate(= Awake,拿到最终参数值) const auto view = reg.view(); for (auto [e, sc] : view.each()) { const u32 bits = static_cast(entt::to_integral(e)); auto& list = m_Instances[bits]; for (const auto& item : sc.items) { if (item.name.empty()) { continue; } const bool exists = std::any_of( list.begin(), list.end(), [&](const ScriptInstance& inst) { return inst.behavior && inst.behavior->Name() == item.name; }); if (exists) { continue; } auto behavior = ScriptRegistry::Create(item.name); if (!behavior) { ENGINE_WARN("Script '{}' not registered (entity {})", item.name, bits); continue; } behavior->Bind(m_Scene, e, m_App); ParamBuilder pb; behavior->DeclareParams(pb); pb.Apply(item.params); behavior->SetParamBinds(pb.TakeBinds()); behavior->OnParamsApplied(); ScriptInstance inst; inst.behavior = std::move(behavior); inst.behavior->OnCreate(); // Unity Awake 语义:实例化即回调 inst.created = true; list.push_back(std::move(inst)); } } } // ---- 参数热刷新(编辑期一次性):重新声明 + 按存档灌值 ---- void ScriptRuntime::RefreshParams(u32 entityBits) { const auto it = m_Instances.find(entityBits); if (it == m_Instances.end()) { return; } auto& reg = m_Scene.GetRegistry(); const auto e = static_cast(entityBits); const ECS::ScriptComponent* sc = reg.valid(e) ? reg.try_get(e) : nullptr; if (!sc) { return; } for (auto& inst : it->second) { if (!inst.behavior) { continue; } ParamBuilder pb; inst.behavior->DeclareParams(pb); const auto item = std::find_if( sc->items.begin(), sc->items.end(), [&](const ECS::ScriptComponent::Item& it2) { return it2.name == inst.behavior->Name(); }); if (item != sc->items.end()) { pb.Apply(item->params); } inst.behavior->SetParamBinds(pb.TakeBinds()); inst.behavior->OnParamsApplied(); } } // ---- 跨脚本按名参数访问 ---- bool ScriptRuntime::SetParam(u32 entityBits, const std::string& scriptName, const std::string& param, const ParamValue& value) { auto& reg = m_Scene.GetRegistry(); const auto e = static_cast(entityBits); ECS::ScriptComponent* sc = reg.valid(e) ? reg.try_get(e) : nullptr; if (!sc) { return false; } const auto item = std::find_if( sc->items.begin(), sc->items.end(), [&](const ECS::ScriptComponent::Item& it) { return it.name == scriptName; }); if (item == sc->items.end()) { return false; } // 目标实例成员直写(实例未建/未声明则跳过,存档仍写穿) const auto ii = m_Instances.find(entityBits); if (ii != m_Instances.end()) { for (auto& inst : ii->second) { if (inst.behavior && inst.behavior->Name() == scriptName) { inst.behavior->WriteParam(param, value); break; } } } // 写穿 Item.params:首次写新名一次分配,此后零分配 const auto pit = std::find_if( item->params.begin(), item->params.end(), [&](const ScriptParam& p) { return p.name == param; }); if (pit != item->params.end()) { pit->value = value; } else { item->params.push_back({param, value}); } return true; } bool ScriptRuntime::GetParam(u32 entityBits, const std::string& scriptName, const std::string& param, ParamValue& out) const { const auto ii = m_Instances.find(entityBits); if (ii == m_Instances.end()) { return false; } for (const auto& inst : ii->second) { if (inst.behavior && inst.behavior->Name() == scriptName) { return inst.behavior->ReadParam(param, out); } } return false; } } // namespace Scripting } // namespace Engine