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:
@@ -36,6 +36,7 @@ namespace Engine {
|
||||
class Renderer;
|
||||
class ImGuiLayer;
|
||||
class PhysicsWorld;
|
||||
namespace Scripting { class ScriptRuntime; }
|
||||
|
||||
namespace ECS { class Scene; }
|
||||
namespace Assets { class AssetManager; }
|
||||
@@ -73,6 +74,7 @@ namespace Engine {
|
||||
ECS::Scene& GetScene() const { return *m_Scene; }
|
||||
Assets::AssetManager& GetAssets() const { return *m_Assets; }
|
||||
PhysicsWorld& GetPhysics() const { return *m_Physics; }
|
||||
Scripting::ScriptRuntime& GetScripts() const { return *m_Scripts; }
|
||||
u64 GetFrameCount() const { return m_FrameCount; }
|
||||
|
||||
// ImGui相关
|
||||
@@ -120,6 +122,7 @@ namespace Engine {
|
||||
Scope<Renderer> m_Renderer;
|
||||
Scope<Assets::AssetManager> m_Assets;
|
||||
Scope<PhysicsWorld> m_Physics; // Jolt 物理(Run 起循环时创建)
|
||||
Scope<Scripting::ScriptRuntime> m_Scripts; // 脚本生命周期(Run 起循环时创建)
|
||||
// 注意成员序:ImGuiLayer 必须后声明(先于 Renderer/Window 析构,
|
||||
// 其后端销毁需要 Device 与 GLFWwindow 存活)
|
||||
Scope<ImGuiLayer> m_ImGuiLayer;
|
||||
|
||||
@@ -274,6 +274,15 @@ namespace Engine::ECS {
|
||||
// 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯
|
||||
};
|
||||
|
||||
// 脚本组件:只存脚本类名(注册表键,POD 可序列化)。C++ 原生脚本经
|
||||
// ENGINE_REGISTER_SCRIPT 自注册;将来 Lua/C# 脚本同键解析。
|
||||
// 实例生命周期由 ScriptRuntime 对账管理(不进组件,保持可拷贝)
|
||||
struct ScriptComponent {
|
||||
std::string scriptName;
|
||||
|
||||
bool operator==(const ScriptComponent&) const = default;
|
||||
};
|
||||
|
||||
} // namespace Engine::ECS
|
||||
|
||||
#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
|
||||
Reference in New Issue
Block a user