新增脚本参数系统:声明式参数 + 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
@@ -22,6 +22,7 @@
#include <Engine/Assets/AssetManager.h>
#include <Engine/Core/Log.h>
#include <Engine/Core/UUID.h>
#include <Engine/Scripting/Script.h>
#include <yaml-cpp/yaml.h>
@@ -50,6 +51,11 @@ namespace Engine::Serialization {
<< YAML::EndSeq;
}
void EmitVec2(YAML::Emitter& e, const glm::vec2& v)
{
e << YAML::Flow << YAML::BeginSeq << v.x << v.y << YAML::EndSeq;
}
void EmitVec4(YAML::Emitter& e, const glm::vec4& v)
{
e << YAML::Flow << YAML::BeginSeq << v.x << v.y << v.z << v.w
@@ -71,6 +77,71 @@ namespace Engine::Serialization {
n[2].as<f32>(def.z)};
}
glm::vec2 ReadVec2(const YAML::Node& n, const glm::vec2& def)
{
if (!n || !n.IsSequence() || n.size() < 2) return def;
return {n[0].as<f32>(def.x), n[1].as<f32>(def.y)};
}
// ---- 脚本参数读端:按声明 schema 收值(调用方保证键已知)----
// 数值臂互转(YAML 标量无类型:"3" 作 f32/i32 均自然,i32 声明
// 遇浮点值截断);形不符回声明默认 + 警告(加载期一次,不刷屏)
template<typename T>
bool TryAs(const YAML::Node& n, T& out)
{
try {
out = n.as<T>();
return true;
} catch (const std::exception&) {
return false; // 类型不符 / 僵尸节点硬化异常统一兜住
}
}
Scripting::ParamValue ReadParamValue(
const YAML::Node& n, const Scripting::ParamBuilder::Desc& d)
{
switch (d.type) {
case Scripting::ParamType::F32: {
f32 v{};
if (TryAs<f32>(n, v)) return {v};
i32 iv{};
if (TryAs<i32>(n, iv)) return {static_cast<f32>(iv)};
break;
}
case Scripting::ParamType::I32: {
i32 v{};
if (TryAs<i32>(n, v)) return {v};
f32 fv{};
if (TryAs<f32>(n, fv)) return {static_cast<i32>(fv)};
break;
}
case Scripting::ParamType::Bool: {
bool v{};
if (TryAs<bool>(n, v)) return {v};
break;
}
case Scripting::ParamType::String: {
if (n.IsScalar()) return {n.Scalar()};
break;
}
case Scripting::ParamType::Vec2: {
if (n && n.IsSequence() && n.size() >= 2) {
return {ReadVec2(n, glm::vec2{0.0f})};
}
break;
}
case Scripting::ParamType::Vec3: {
if (n && n.IsSequence() && n.size() >= 3) {
return {ReadVec3(n, glm::vec3{0.0f})};
}
break;
}
}
ENGINE_WARN("Script param '{}' type mismatch, use default",
d.name);
return d.def;
}
glm::vec4 ReadVec4(const YAML::Node& n, const glm::vec4& def)
{
if (!n || !n.IsSequence() || n.size() < 4) return def;
@@ -452,6 +523,8 @@ namespace Engine::Serialization {
<< cc->maxSlopeAngle;
out << YAML::Key << "StepHeight" << YAML::Value
<< cc->stepHeight;
out << YAML::Key << "GravityScale" << YAML::Value
<< cc->gravityScale;
out << YAML::Key << "Active" << YAML::Value << cc->active;
out << YAML::EndMap;
}
@@ -467,6 +540,39 @@ namespace Engine::Serialization {
out << YAML::BeginMap;
out << YAML::Key << "Name" << YAML::Value << item.name;
out << YAML::Key << "Active" << YAML::Value << item.active;
if (!item.params.empty()) {
out << YAML::Key << "Params" << YAML::Value
<< YAML::BeginMap;
for (const auto& p : item.params) {
switch (p.value.Type()) {
case Engine::Scripting::ParamType::F32:
out << YAML::Key << p.name << YAML::Value
<< std::get<f32>(p.value.v);
break;
case Engine::Scripting::ParamType::I32:
out << YAML::Key << p.name << YAML::Value
<< std::get<i32>(p.value.v);
break;
case Engine::Scripting::ParamType::Bool:
out << YAML::Key << p.name << YAML::Value
<< std::get<bool>(p.value.v);
break;
case Engine::Scripting::ParamType::String:
out << YAML::Key << p.name << YAML::Value
<< std::get<std::string>(p.value.v);
break;
case Engine::Scripting::ParamType::Vec2:
out << YAML::Key << p.name << YAML::Value;
EmitVec2(out, std::get<glm::vec2>(p.value.v));
break;
case Engine::Scripting::ParamType::Vec3:
out << YAML::Key << p.name << YAML::Value;
EmitVec3(out, std::get<glm::vec3>(p.value.v));
break;
}
}
out << YAML::EndMap;
}
out << YAML::EndMap;
}
out << YAML::EndSeq;
@@ -855,6 +961,7 @@ namespace Engine::Serialization {
cc.mass = n["Mass"].as<f32>(cc.mass);
cc.maxSlopeAngle = n["MaxSlopeAngle"].as<f32>(cc.maxSlopeAngle);
cc.stepHeight = n["StepHeight"].as<f32>(cc.stepHeight);
cc.gravityScale = n["GravityScale"].as<f32>(cc.gravityScale);
cc.active = n["Active"].as<bool>(true);
// 状态(速度/着地)由 PhysicsWorld 对账重建,不存
}
@@ -868,6 +975,27 @@ namespace Engine::Serialization {
ECS::ScriptComponent::Item item;
item.name = it["Name"].as<std::string>("");
item.active = it["Active"].as<bool>(true);
// 参数:按脚本 schema 对齐收值——未知键忽略(前向
// 兼容),缺失键不存(运行时用声明默认)
const YAML::Node ps = it["Params"];
if (ps) {
const auto& schema =
Scripting::ScriptRegistry::Schema(item.name);
for (const auto& kv : ps) {
const std::string pname =
kv.first.as<std::string>("");
const auto dit = std::find_if(
schema.begin(), schema.end(),
[&](const Scripting::ParamBuilder::Desc& d) {
return d.name == pname;
});
if (dit == schema.end()) {
continue;
}
item.params.push_back(
{pname, ReadParamValue(kv.second, *dit)});
}
}
// 去重兜底:同名项只收第一份(UI 已杜绝重复,
// 手编场景仍可能写出重复)
const bool dup = std::any_of(