新增脚本参数系统:声明式参数 + Inspector 自动控件 + 场景序列化

DeclareParams/ParamBuilder 声明字段,运行时反射读写;
Inspector 按类型生成控件,SceneSerializer 直通存取;
动画系统接入参数化裁剪;新增 OrbitMove 示例脚本
This commit is contained in:
2026-10-08 17:02:19 +08:00
parent f327f44269
commit ff955b9e63
11 changed files with 951 additions and 33 deletions
+120
View File
@@ -17,6 +17,7 @@
#include "Engine/Renderer/MaterialAsset.h" // 材质覆盖槽(.material 资产)
#include "Engine/Physics/PhysicsWorld.h" // Apply Impulse 调试(GetPhysics 门面)
#include "Engine/Scripting/Script.h" // 脚本下拉(ScriptRegistry::Names)
#include "Engine/Scripting/ScriptRuntime.h" // 参数编辑 → RefreshParams
#include "Engine/Core/Log.h" // Create Override 失败告警
#include <imgui.h>
@@ -38,6 +39,28 @@ namespace
// 材质槽循环索引用(u32 定义于 namespace Engine,App 层显式引入)
using Engine::u32;
// 脚本参数对齐 schema:缺失 → 声明默认补齐;类型不符 → 重置默认
//(换脚本类/手编档防呆)。返回对齐后的参数项指针(每帧幂等)
Engine::Scripting::ScriptParam* EnsureParam(
Engine::ECS::ScriptComponent::Item& item,
const Engine::Scripting::ParamBuilder::Desc& d)
{
auto it = std::find_if(
item.params.begin(), item.params.end(),
[&](const Engine::Scripting::ScriptParam& p)
{ return p.name == d.name; });
if (it == item.params.end())
{
item.params.push_back({d.name, d.def});
return &item.params.back();
}
if (it->value.Type() != d.type)
{
it->value = d.def;
}
return &*it;
}
// 递归收集 assets/ 下模型容器文件(模型槽与动画 attach 槽共用;
// 无法从扩展名区分"模型 fbx"与"纯动画 fbx",一并列出)。
// 会话级缓存:目录条目几十量级,避免每帧递归扫描
@@ -1156,6 +1179,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
ImGui::SeparatorText("Movement");
ImGui::DragFloat("Max Slope (deg)", &cc->maxSlopeAngle, 1.0f, 1.0f, 89.0f);
ImGui::DragFloat("Step Height", &cc->stepHeight, 0.02f, 0.0f, 2.0f);
ImGui::DragFloat("Gravity Scale", &cc->gravityScale, 0.1f, 0.0f, 10.0f);
// ---- Debug:遥测(只读,运行时状态在 PhysicsWorld)----
if (auto& phy = ctx.app->GetPhysics(); phy.IsEnabled())
@@ -1249,6 +1273,102 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
{
removeIdx = i;
}
// ---- 参数区(按脚本 schema 自动生成;缺省补齐 + 类型
// 防呆;编辑 → RefreshParams 重灌实例成员,PIE 当帧生效)----
if (!item.name.empty() &&
!Engine::Scripting::ScriptRegistry::Schema(item.name)
.empty())
{
const auto& schema =
Engine::Scripting::ScriptRegistry::Schema(item.name);
ImGui::Indent();
bool anyEdited = false;
for (const auto& d : schema)
{
auto* p = EnsureParam(item, d);
bool changed = false;
// 局部变量禁用 f32(glm::f32 歧义前科),用 float
switch (d.type)
{
case Engine::Scripting::ParamType::F32:
{
float v = std::get<float>(p->value.v);
if (ImGui::DragFloat(d.name.c_str(), &v, 0.1f))
{
p->value = Engine::Scripting::ParamValue{v};
changed = true;
}
break;
}
case Engine::Scripting::ParamType::I32:
{
int v = std::get<Engine::i32>(p->value.v);
if (ImGui::DragInt(d.name.c_str(), &v))
{
p->value = Engine::Scripting::ParamValue{
static_cast<Engine::i32>(v)};
changed = true;
}
break;
}
case Engine::Scripting::ParamType::Bool:
changed = ImGui::Checkbox(
d.name.c_str(), &std::get<bool>(p->value.v));
break;
case Engine::Scripting::ParamType::String:
{
char buf[256] = {};
std::snprintf(buf, sizeof(buf), "%s",
std::get<std::string>(p->value.v)
.c_str());
if (ImGui::InputText(d.name.c_str(), buf,
sizeof(buf)))
{
p->value = Engine::Scripting::ParamValue{
std::string(buf)};
changed = true;
}
break;
}
case Engine::Scripting::ParamType::Vec2:
{
const glm::vec2 g =
std::get<glm::vec2>(p->value.v);
float v[2] = {g.x, g.y};
if (ImGui::DragFloat2(d.name.c_str(), v, 0.1f))
{
p->value = Engine::Scripting::ParamValue{
glm::vec2(v[0], v[1])};
changed = true;
}
break;
}
case Engine::Scripting::ParamType::Vec3:
{
const glm::vec3 g =
std::get<glm::vec3>(p->value.v);
float v[3] = {g.x, g.y, g.z};
if (ImGui::DragFloat3(d.name.c_str(), v, 0.1f))
{
p->value = Engine::Scripting::ParamValue{
glm::vec3(v[0], v[1], v[2])};
changed = true;
}
break;
}
}
if (changed)
{
anyEdited = true;
}
}
if (anyEdited)
{
ctx.app->GetScripts().RefreshParams(
static_cast<uint32_t>(entt::to_integral(selected)));
}
ImGui::Unindent();
}
ImGui::PopID();
// 已填名但注册表查无此类:场景加载早于脚本注册/改名都会走到这
if (!item.name.empty() && cur == 0)
+46
View File
@@ -0,0 +1,46 @@
//
// OrbitMove.cpp:参数管线验证样本——实体绕中心点圆周运动。
//
// 参数全链路:DeclareParams 声明(Speed/Radius/Center)→ Inspector
// 自动出控件 → 随场景序列化 → 实例化/RefreshParams 灌值。PIE 中拖
// Radius 当帧生效;Stop 由快照天然回滚。
// 未声明成员(m_Angle)= 纯运行时状态,不进 Inspector 不落盘。
//
#include <Engine/Scripting/Script.h>
#include <glm/glm.hpp>
#include <cmath>
namespace {
class OrbitMove final : public Engine::Scripting::ScriptBehavior {
public:
const char* Name() const override { return "OrbitMove"; }
// 参数声明(Inspector 控件 + 序列化 + 灌值共用 schema)
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
pb.Field("Speed", &m_Speed, 1.0f); // 角速度(弧度/秒)
pb.Field("Radius", &m_Radius, 3.0f); // 轨道半径
pb.Field("Center", &m_Center, glm::vec3{0.0f}); // 轨道中心
}
void OnUpdate(float dt) override {
m_Angle += m_Speed * dt;
// 直接驱动 Transform(相对父空间的圆周;无父 = 世界)
Transform().SetPosition(
m_Center + glm::vec3(std::cos(m_Angle) * m_Radius, 0.0f,
std::sin(m_Angle) * m_Radius));
}
private:
float m_Speed = 1.0f;
float m_Radius = 3.0f;
glm::vec3 m_Center{0.0f};
float m_Angle = 0.0f; // 运行时相位(非参数)
};
ENGINE_REGISTER_SCRIPT(OrbitMove);
} // namespace
+9 -3
View File
@@ -1,10 +1,12 @@
//
// Spinner.cpp:C++ 原生脚本示范——Y 轴匀速自转。
//
// 写脚本三步:
// 写脚本四步:
// 1. 新建 .cpp 放本目录(CMake 自动进构建)
// 2. 继承 ScriptBehavior,覆写 Name/OnCreate/OnUpdate/OnDestroy
// 3. 文件级 ENGINE_REGISTER_SCRIPT(类名) 自注册——Inspector 下拉
// 3. DeclareParams 声明可调参数(Inspector 自动出控件 + 随场景
// 序列化 + 实例化灌值)
// 4. 文件级 ENGINE_REGISTER_SCRIPT(类名) 自注册——Inspector 下拉
// 与场景序列化按该名解析
//
@@ -18,6 +20,10 @@ namespace {
public:
const char* Name() const override { return "Spinner"; }
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
pb.Field("Speed", &m_Speed, 90.0f); // 度/秒
}
void OnCreate() override {
// 实体与组件已就位;需要别的组件时用 Get<T>()(不存在会炸)
}
@@ -31,7 +37,7 @@ namespace {
}
private:
float m_Speed = 90.0f; // 度/秒
float m_Speed = 90.0f; // 度/秒(声明默认;实例化期被存档值覆盖)
};
ENGINE_REGISTER_SCRIPT(Spinner);