Files
HelloVulkan/Engine/include/Engine/Scripting/Script.h
T
atdunbg ff955b9e63 新增脚本参数系统:声明式参数 + Inspector 自动控件 + 场景序列化
DeclareParams/ParamBuilder 声明字段,运行时反射读写;
Inspector 按类型生成控件,SceneSerializer 直通存取;
动画系统接入参数化裁剪;新增 OrbitMove 示例脚本
2026-10-08 17:02:19 +08:00

251 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Script.h:C++ 原生脚本设施(行为基类 + 按名注册表)。
//
// 设计参照 UE:脚本 = 挂在实体上的行为对象 + 统一基类生命周期
//(OnCreate/OnUpdate/OnDestroy,BeginPlay/Tick 语义)。引擎帧循环只认
// 基类接口——将来接 Lua/C# 时各实现一种 foreign behavior(内部持
// VM 状态),引擎零改动。
//
// 瘦基类(对齐 Unity6/UE5 现役形态):基类只供生命周期 + 参数 +
// 实体访问器,零领域 API——物理住 PhysicsWorld(App().GetPhysics()
// 直调),动画住 AnimationComponent(GetComponent 直调,Animator
// 同形)。DeclareParams = C# 反射缺失下 UPROPERTY 的等价物:Unity
// 里你从不写声明是因为语言替你做了,C++ 里每个引擎都得补这一环
//(UE 让你标 UPROPERTY,这里让你写一行 Declare)。
//
// 组件只存 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 <Engine/Scripting/ScriptParam.h>
#include <string>
#include <unordered_map>
#include <vector>
#include <algorithm>
namespace Engine {
class Application; // 宿主门面(Window/Renderer/Physics 入口),实现见 ScriptRuntime.cpp
namespace Scripting {
class ScriptBehavior {
public:
virtual ~ScriptBehavior() = default;
// 注册表键(= 组件 scriptName),须与 ENGINE_REGISTER_SCRIPT 名一致
virtual const char* Name() const = 0;
// 生命周期(吃 scaled dt:编辑器暂停 timeScale=0 时自然停摆)。
// 顺序对齐 Unity:OnCreate(= Awake,实例化即调)→ OnEnable
// → OnStart(首次 OnUpdate 前一次)→ OnUpdate* → OnDisable →
// OnDestroy(可能因实体销毁触发,句柄已失效只做自身清理)。
// 跨实体 OnEnable 顺序不保证(实例表无序),与 Unity 一致
virtual void OnCreate() {}
virtual void OnEnable() {}
virtual void OnStart() {}
virtual void OnUpdate(f32 dt) {}
virtual void OnDisable() {}
virtual void OnDestroy() {}
// 参数声明(实例化灌值与注册表 schema 缓存共用入口;外语绑定
// 同键接入)。契约:只写成员指针与常量,禁触场景/应用——
// schema 缓存路径用未 Bind 的临时实例调用
virtual void DeclareParams(ParamBuilder& pb) { (void)pb; }
// 参数灌值后回调(外语侧收值接缝:C++ 成员已直写,VM 绑定
// 在此按名取值)
virtual void OnParamsApplied() {}
protected:
// 便捷访问:绑定实体的 TransformComponent
ECS::TransformComponent& Transform() {
return m_Scene->GetRegistry().get<ECS::TransformComponent>(m_Entity);
}
template<typename T>
T& GetComponent() {
return m_Scene->GetRegistry().get<T>(m_Entity);
}
template<typename T>
T* TryGetComponent() {
return m_Scene->GetRegistry().try_get<T>(m_Entity);
}
// 绑定实体的 entityBits(PhysicsWorld 按此键寻址:直调
// App().GetPhysics() 的各 API 时作第一个参数)
u32 EntityBits() const {
return static_cast<u32>(entt::to_integral(m_Entity));
}
// 宿主应用(GetWindow/GetRenderer/GetPhysics/GetScene……)
Application& App() const;
bool Valid() const { return m_Scene != nullptr && m_Entity != entt::null; }
// ---- 组件启停(Unity SetActive / Behaviour.enabled 语义)----
// 自身组件总开关(整个脚本组件停 tick,实例保留)
void SetActive(bool on) {
if (auto* sc = m_Scene->GetRegistry().try_get<ECS::ScriptComponent>(m_Entity)) {
sc->active = on;
}
}
bool IsActive() const {
const auto* sc =
m_Scene->GetRegistry().try_get<ECS::ScriptComponent>(m_Entity);
return sc != nullptr && sc->active;
}
// ---- 自身参数(写成员 + 写穿 Item.params:PIE Inspector
// 实时反映,Stop 由快照天然回滚;首次写新名一次分配)----
// 返回成员直写是否命中(未声明参数 false,存档仍记录——前向
// 兼容)。跨脚本/跨实体访问走 App().GetScripts().SetParam/GetParam
bool SetParam(const std::string& name, const ParamValue& v) {
const bool hit = WriteParam(name, v);
if (auto* sc = Valid()
? m_Scene->GetRegistry().try_get<ECS::ScriptComponent>(m_Entity)
: nullptr) {
const auto item = std::find_if(
sc->items.begin(), sc->items.end(),
[&](const ECS::ScriptComponent::Item& it) {
return it.name == Name();
});
if (item != sc->items.end()) {
const auto pit = std::find_if(
item->params.begin(), item->params.end(),
[&](const ScriptParam& p) { return p.name == name; });
if (pit != item->params.end()) {
pit->value = v;
} else {
item->params.push_back({name, v});
}
}
}
return hit;
}
template<typename T>
bool SetParam(const std::string& name, const T& v) {
return SetParam(name, ParamValue{v});
}
bool GetParam(const std::string& name, ParamValue& out) const {
return ReadParam(name, out);
}
template<typename T>
bool GetParam(const std::string& name, T& out) const {
ParamValue v;
if (!ReadParam(name, v)) {
return false;
}
if (auto* p = std::get_if<T>(&v.v)) {
out = *p;
return true;
}
return false; // 类型不符
}
private:
friend class ScriptRuntime;
void Bind(ECS::Scene& scene, entt::entity e, Application* app) {
m_Scene = &scene;
m_Entity = e;
m_App = app;
}
// 参数绑定表交接(实例化/RefreshParams 时由运行时填充)
void SetParamBinds(std::vector<ParamBuilder::Bind> binds) {
m_ParamBinds = std::move(binds);
}
// 按名写/读成员(经绑定表;未声明或类型不符返回 false)。
// 写穿 Item.params 由运行时侧完成(需查组件项)
bool WriteParam(const std::string& name, const ParamValue& v);
bool ReadParam(const std::string& name, ParamValue& out) const;
ECS::Scene* m_Scene = nullptr;
entt::entity m_Entity = entt::null;
Application* m_App = nullptr;
std::vector<ParamBuilder::Bind> m_ParamBinds;
};
// ---- 按名注册表:脚本类文件级自注册,运行时按名实例化 ----
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;
}
// 参数 schema(惰性缓存):临时实例调 DeclareParams 取声明
// ——每脚本类一次性(注册先于 main,加载期必就绪)。未注册
// 脚本 = 空 schema(读端未知键全忽略,弱前向兼容)
static const std::vector<ParamBuilder::Desc>&
Schema(const std::string& name) {
static const std::vector<ParamBuilder::Desc> kEmpty;
static std::unordered_map<std::string,
std::vector<ParamBuilder::Desc>>
cache;
const auto it = cache.find(name);
if (it != cache.end()) {
return it->second;
}
std::vector<ParamBuilder::Desc> descs;
if (auto behavior = Create(name)) {
ParamBuilder pb;
behavior->DeclareParams(pb);
descs = pb.Descs();
}
return cache.emplace(name, std::move(descs)).first->second;
}
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