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:
+5
-2
@@ -1,9 +1,12 @@
|
|||||||
# App 层源文件:main + editor/(docking 编辑器,纯 App 代码)
|
# App 层源文件:main + editor/(docking 编辑器)+ scripts/(C++ 原生脚本)
|
||||||
file(GLOB EDITOR_SOURCES CONFIGURE_DEPENDS
|
file(GLOB EDITOR_SOURCES CONFIGURE_DEPENDS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/editor/*.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/editor/*.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/editor/*.h)
|
${CMAKE_CURRENT_SOURCE_DIR}/editor/*.h)
|
||||||
|
|
||||||
add_executable(HelloVulkan main.cpp ${EDITOR_SOURCES})
|
file(GLOB SCRIPT_SOURCES CONFIGURE_DEPENDS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.cpp)
|
||||||
|
|
||||||
|
add_executable(HelloVulkan main.cpp ${EDITOR_SOURCES} ${SCRIPT_SOURCES})
|
||||||
|
|
||||||
target_include_directories(HelloVulkan PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/editor)
|
target_include_directories(HelloVulkan PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/editor)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "Engine/ECS/Components.h"
|
#include "Engine/ECS/Components.h"
|
||||||
#include "Engine/Renderer/Model.h"
|
#include "Engine/Renderer/Model.h"
|
||||||
#include "Engine/Physics/PhysicsWorld.h" // Apply Impulse 调试(GetPhysics 门面)
|
#include "Engine/Physics/PhysicsWorld.h" // Apply Impulse 调试(GetPhysics 门面)
|
||||||
|
#include "Engine/Scripting/Script.h" // 脚本下拉(ScriptRegistry::Names)
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
@@ -126,6 +127,11 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
{
|
{
|
||||||
reg.emplace<Engine::ECS::CharacterControllerComponent>(selected);
|
reg.emplace<Engine::ECS::CharacterControllerComponent>(selected);
|
||||||
}
|
}
|
||||||
|
if (!reg.all_of<Engine::ECS::ScriptComponent>(selected) &&
|
||||||
|
ImGui::MenuItem("Script"))
|
||||||
|
{
|
||||||
|
reg.emplace<Engine::ECS::ScriptComponent>(selected);
|
||||||
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
@@ -428,6 +434,39 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Script(按名解析注册表脚本;实例生命周期在 ScriptRuntime)----
|
||||||
|
if (auto* sc = reg.try_get<Engine::ECS::ScriptComponent>(selected))
|
||||||
|
{
|
||||||
|
if (ImGui::CollapsingHeader("Script", ImGuiTreeNodeFlags_DefaultOpen))
|
||||||
|
{
|
||||||
|
// 下拉列出全部已注册脚本(注册序);"<none>" = 清空(对账自动销毁实例)
|
||||||
|
const auto& names = Engine::Scripting::ScriptRegistry::Names();
|
||||||
|
std::vector<const char*> items;
|
||||||
|
items.push_back("<none>");
|
||||||
|
for (const auto& n : names)
|
||||||
|
{
|
||||||
|
items.push_back(n.c_str());
|
||||||
|
}
|
||||||
|
int cur = 0;
|
||||||
|
for (size_t i = 0; i < names.size(); ++i)
|
||||||
|
{
|
||||||
|
if (names[i] == sc->scriptName)
|
||||||
|
{
|
||||||
|
cur = static_cast<int>(i) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ImGui::Combo("script", &cur, items.data(),
|
||||||
|
static_cast<int>(items.size())))
|
||||||
|
{
|
||||||
|
sc->scriptName = (cur <= 0) ? "" : names[static_cast<size_t>(cur) - 1];
|
||||||
|
}
|
||||||
|
if (sc->scriptName.empty())
|
||||||
|
{
|
||||||
|
ImGui::TextDisabled("no script assigned");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Animation ----
|
// ---- Animation ----
|
||||||
if (auto* anim = reg.try_get<Engine::ECS::AnimationComponent>(selected))
|
if (auto* anim = reg.try_get<Engine::ECS::AnimationComponent>(selected))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// Spinner.cpp:C++ 原生脚本示范——Y 轴匀速自转。
|
||||||
|
//
|
||||||
|
// 写脚本三步:
|
||||||
|
// 1. 新建 .cpp 放本目录(CMake 自动进构建)
|
||||||
|
// 2. 继承 ScriptBehavior,覆写 Name/OnCreate/OnUpdate/OnDestroy
|
||||||
|
// 3. 文件级 ENGINE_REGISTER_SCRIPT(类名) 自注册——Inspector 下拉
|
||||||
|
// 与场景序列化按该名解析
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Scripting/Script.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class Spinner final : public Engine::Scripting::ScriptBehavior {
|
||||||
|
public:
|
||||||
|
const char* Name() const override { return "Spinner"; }
|
||||||
|
|
||||||
|
void OnCreate() override {
|
||||||
|
// 实体与组件已就位;需要别的组件时用 Get<T>()(不存在会炸)
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnUpdate(float dt) override {
|
||||||
|
// 四元数复合绕 Y(避免每帧欧拉↔四元数往返累积失真)
|
||||||
|
const glm::quat step = glm::angleAxis(
|
||||||
|
glm::radians(m_Speed * dt), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
|
Transform().SetRotationQuat(step * Transform().GetRotationQuat());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
float m_Speed = 90.0f; // 度/秒
|
||||||
|
};
|
||||||
|
|
||||||
|
ENGINE_REGISTER_SCRIPT(Spinner)
|
||||||
|
|
||||||
|
} // namespace
|
||||||
@@ -36,6 +36,7 @@ namespace Engine {
|
|||||||
class Renderer;
|
class Renderer;
|
||||||
class ImGuiLayer;
|
class ImGuiLayer;
|
||||||
class PhysicsWorld;
|
class PhysicsWorld;
|
||||||
|
namespace Scripting { class ScriptRuntime; }
|
||||||
|
|
||||||
namespace ECS { class Scene; }
|
namespace ECS { class Scene; }
|
||||||
namespace Assets { class AssetManager; }
|
namespace Assets { class AssetManager; }
|
||||||
@@ -73,6 +74,7 @@ namespace Engine {
|
|||||||
ECS::Scene& GetScene() const { return *m_Scene; }
|
ECS::Scene& GetScene() const { return *m_Scene; }
|
||||||
Assets::AssetManager& GetAssets() const { return *m_Assets; }
|
Assets::AssetManager& GetAssets() const { return *m_Assets; }
|
||||||
PhysicsWorld& GetPhysics() const { return *m_Physics; }
|
PhysicsWorld& GetPhysics() const { return *m_Physics; }
|
||||||
|
Scripting::ScriptRuntime& GetScripts() const { return *m_Scripts; }
|
||||||
u64 GetFrameCount() const { return m_FrameCount; }
|
u64 GetFrameCount() const { return m_FrameCount; }
|
||||||
|
|
||||||
// ImGui相关
|
// ImGui相关
|
||||||
@@ -120,6 +122,7 @@ namespace Engine {
|
|||||||
Scope<Renderer> m_Renderer;
|
Scope<Renderer> m_Renderer;
|
||||||
Scope<Assets::AssetManager> m_Assets;
|
Scope<Assets::AssetManager> m_Assets;
|
||||||
Scope<PhysicsWorld> m_Physics; // Jolt 物理(Run 起循环时创建)
|
Scope<PhysicsWorld> m_Physics; // Jolt 物理(Run 起循环时创建)
|
||||||
|
Scope<Scripting::ScriptRuntime> m_Scripts; // 脚本生命周期(Run 起循环时创建)
|
||||||
// 注意成员序:ImGuiLayer 必须后声明(先于 Renderer/Window 析构,
|
// 注意成员序:ImGuiLayer 必须后声明(先于 Renderer/Window 析构,
|
||||||
// 其后端销毁需要 Device 与 GLFWwindow 存活)
|
// 其后端销毁需要 Device 与 GLFWwindow 存活)
|
||||||
Scope<ImGuiLayer> m_ImGuiLayer;
|
Scope<ImGuiLayer> m_ImGuiLayer;
|
||||||
|
|||||||
@@ -274,6 +274,15 @@ namespace Engine::ECS {
|
|||||||
// 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯
|
// 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 脚本组件:只存脚本类名(注册表键,POD 可序列化)。C++ 原生脚本经
|
||||||
|
// ENGINE_REGISTER_SCRIPT 自注册;将来 Lua/C# 脚本同键解析。
|
||||||
|
// 实例生命周期由 ScriptRuntime 对账管理(不进组件,保持可拷贝)
|
||||||
|
struct ScriptComponent {
|
||||||
|
std::string scriptName;
|
||||||
|
|
||||||
|
bool operator==(const ScriptComponent&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Engine::ECS
|
} // namespace Engine::ECS
|
||||||
|
|
||||||
#endif //HELLOVULKAN_COMPONENTS_H
|
#endif //HELLOVULKAN_COMPONENTS_H
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// Script.h:C++ 原生脚本设施(行为基类 + 按名注册表)。
|
||||||
|
//
|
||||||
|
// 设计参照 UE:脚本 = 挂在实体上的行为对象 + 统一基类生命周期
|
||||||
|
//(OnCreate/OnUpdate/OnDestroy,BeginPlay/Tick 语义)。引擎帧循环只认
|
||||||
|
// 基类接口——将来接 Lua/C# 时各实现一种 foreign behavior(内部持
|
||||||
|
// VM 状态),引擎零改动。
|
||||||
|
//
|
||||||
|
// 组件只存 scriptName 字符串(POD,序列化天然支持:Lua/C# 同样按名
|
||||||
|
// 解析);实例由 ScriptRuntime 对账式管理(照 PhysicsWorld 模式),
|
||||||
|
// 不进组件——组件可拷贝/可比较。
|
||||||
|
//
|
||||||
|
// 脚本作者注意:
|
||||||
|
// OnCreate/OnUpdate 阶段实体与组件已就位(假定所需组件存在);
|
||||||
|
// OnDestroy 只做自身清理——可能因实体销毁触发,句柄已失效。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SCRIPT_H
|
||||||
|
#define HELLOVULKAN_SCRIPT_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/ECS/Components.h>
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
namespace Scripting {
|
||||||
|
|
||||||
|
class ScriptBehavior {
|
||||||
|
public:
|
||||||
|
virtual ~ScriptBehavior() = default;
|
||||||
|
|
||||||
|
// 注册表键(= 组件 scriptName),须与 ENGINE_REGISTER_SCRIPT 名一致
|
||||||
|
virtual const char* Name() const = 0;
|
||||||
|
|
||||||
|
// 生命周期(吃 scaled dt:编辑器暂停 timeScale=0 时自然停摆)
|
||||||
|
virtual void OnCreate() {}
|
||||||
|
virtual void OnUpdate(f32 dt) {}
|
||||||
|
virtual void OnDestroy() {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// 便捷访问:绑定实体的 TransformComponent
|
||||||
|
ECS::TransformComponent& Transform() {
|
||||||
|
return m_Scene->GetRegistry().get<ECS::TransformComponent>(m_Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T& Get() {
|
||||||
|
return m_Scene->GetRegistry().get<T>(m_Entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Valid() const { return m_Scene != nullptr && m_Entity != entt::null; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class ScriptRuntime;
|
||||||
|
void Bind(ECS::Scene& scene, entt::entity e) {
|
||||||
|
m_Scene = &scene;
|
||||||
|
m_Entity = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECS::Scene* m_Scene = nullptr;
|
||||||
|
entt::entity m_Entity = entt::null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- 按名注册表:脚本类文件级自注册,运行时按名实例化 ----
|
||||||
|
class ScriptRegistry {
|
||||||
|
public:
|
||||||
|
using Factory = Scope<ScriptBehavior> (*)();
|
||||||
|
|
||||||
|
static void Register(const std::string& name, Factory f) {
|
||||||
|
Map()[name] = f;
|
||||||
|
Names().push_back(name); // Inspector 下拉用(保持注册序)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Scope<ScriptBehavior> Create(const std::string& name) {
|
||||||
|
auto& m = Map();
|
||||||
|
const auto it = m.find(name);
|
||||||
|
return it != m.end() ? it->second() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool Contains(const std::string& name) {
|
||||||
|
return Map().count(name) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<std::string>& Names() {
|
||||||
|
static std::vector<std::string> v;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::unordered_map<std::string, Factory>& Map() {
|
||||||
|
static std::unordered_map<std::string, Factory> m;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Scripting
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
// 文件级自注册:脚本 cpp 里写一行即可,Inspector 下拉与序列化按
|
||||||
|
// 类名(字符串)解析。T 须默认可构造并继承 ScriptBehavior
|
||||||
|
#define ENGINE_REGISTER_SCRIPT(T) \
|
||||||
|
static const bool s_##T##_registered = [] { \
|
||||||
|
::Engine::Scripting::ScriptRegistry::Register( \
|
||||||
|
#T, []() -> ::Engine::Scope< \
|
||||||
|
::Engine::Scripting::ScriptBehavior> { \
|
||||||
|
return ::Engine::CreateScope<T>(); \
|
||||||
|
}); \
|
||||||
|
return true; \
|
||||||
|
}();
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCRIPT_H
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// ScriptRuntime.h:脚本生命周期驱动。对账式管理(组件加/删/改名 ↔
|
||||||
|
// 实例建/毁),照 PhysicsWorld.SyncScene 模式——每帧对账,编辑器操作
|
||||||
|
// 即时生效,场景加载/实体销毁自动消化。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SCRIPT_RUNTIME_H
|
||||||
|
#define HELLOVULKAN_SCRIPT_RUNTIME_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
#include <Engine/Scripting/Script.h>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
namespace Scripting {
|
||||||
|
|
||||||
|
// 实例簿记:行为对象 + OnCreate 是否已调
|
||||||
|
struct ScriptInstance {
|
||||||
|
Scope<ScriptBehavior> behavior;
|
||||||
|
bool created = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScriptRuntime {
|
||||||
|
public:
|
||||||
|
explicit ScriptRuntime(ECS::Scene& scene) : m_Scene(scene) {}
|
||||||
|
|
||||||
|
// 每帧调用(吃 scaled dt):对账 → OnCreate(新实例)→ OnUpdate
|
||||||
|
void Update(f32 dt);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Sync();
|
||||||
|
|
||||||
|
ECS::Scene& m_Scene;
|
||||||
|
std::unordered_map<u32, ScriptInstance> m_Instances; // entityBits
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Scripting
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCRIPT_RUNTIME_H
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
#include <Engine/ECS/Scene.h>
|
#include <Engine/ECS/Scene.h>
|
||||||
#include <Engine/Physics/PhysicsWorld.h>
|
#include <Engine/Physics/PhysicsWorld.h>
|
||||||
|
#include <Engine/Scripting/ScriptRuntime.h>
|
||||||
#include <Engine/Renderer/Renderer.h>
|
#include <Engine/Renderer/Renderer.h>
|
||||||
#include <Engine/Assets/AssetManager.h>
|
#include <Engine/Assets/AssetManager.h>
|
||||||
#include <Engine/Vulkan/Device.h>
|
#include <Engine/Vulkan/Device.h>
|
||||||
@@ -102,6 +103,9 @@ namespace Engine {
|
|||||||
// 物理调试图元 → 渲染侧(PhysicsDebugPass 每帧上传绘制)
|
// 物理调试图元 → 渲染侧(PhysicsDebugPass 每帧上传绘制)
|
||||||
m_Renderer->SetPhysicsDebugSource(&m_Physics->GetDebugFrame());
|
m_Renderer->SetPhysicsDebugSource(&m_Physics->GetDebugFrame());
|
||||||
|
|
||||||
|
// 脚本生命周期(对账式,组件增删/改名即时生效)
|
||||||
|
m_Scripts = CreateScope<Scripting::ScriptRuntime>(*m_Scene);
|
||||||
|
|
||||||
while (m_Running) {
|
while (m_Running) {
|
||||||
const auto frameStart = std::chrono::steady_clock::now();
|
const auto frameStart = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
@@ -121,6 +125,10 @@ namespace Engine {
|
|||||||
// SyncScene 对账仍跑(拖静态体照常同步)
|
// SyncScene 对账仍跑(拖静态体照常同步)
|
||||||
m_Physics->Step(frame.Dt());
|
m_Physics->Step(frame.Dt());
|
||||||
|
|
||||||
|
// 脚本更新(物理之后、业务 OnUpdate 之前,Unity FixedUpdate→Update
|
||||||
|
// 语义;吃 scaled dt——timeScale=0 暂停时 dt=0 脚本自然停摆)
|
||||||
|
m_Scripts->Update(frame.Dt());
|
||||||
|
|
||||||
OnUpdate(frame);
|
OnUpdate(frame);
|
||||||
|
|
||||||
// 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变
|
// 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -343,6 +343,14 @@ namespace Engine::Serialization {
|
|||||||
out << YAML::EndMap;
|
out << YAML::EndMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 脚本只存类名(注册表键);未注册名字加载后运行时警告跳过
|
||||||
|
if (const auto* sc = reg.try_get<ECS::ScriptComponent>(e)) {
|
||||||
|
out << YAML::Key << "Script" << YAML::Value << YAML::BeginMap;
|
||||||
|
out << YAML::Key << "ScriptName" << YAML::Value
|
||||||
|
<< sc->scriptName;
|
||||||
|
out << YAML::EndMap;
|
||||||
|
}
|
||||||
|
|
||||||
out << YAML::EndMap; // Components
|
out << YAML::EndMap; // Components
|
||||||
out << YAML::EndMap; // Entity
|
out << YAML::EndMap; // Entity
|
||||||
}
|
}
|
||||||
@@ -628,6 +636,12 @@ namespace Engine::Serialization {
|
|||||||
cc.stepHeight = n["StepHeight"].as<f32>(cc.stepHeight);
|
cc.stepHeight = n["StepHeight"].as<f32>(cc.stepHeight);
|
||||||
// 状态(速度/着地)由 PhysicsWorld 对账重建,不存
|
// 状态(速度/着地)由 PhysicsWorld 对账重建,不存
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 脚本:只存类名;未注册名字由 ScriptRuntime 运行时警告跳过
|
||||||
|
if (const YAML::Node n = comps["Script"]) {
|
||||||
|
auto& sc = handle.AddComponent<ECS::ScriptComponent>();
|
||||||
|
sc.scriptName = n["ScriptName"].as<std::string>("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
|
// 加载在 OnImGuiRender 段,错过本帧 UpdateTransforms:
|
||||||
|
|||||||
Reference in New Issue
Block a user