新增脚本参数系统:声明式参数 + Inspector 自动控件 + 场景序列化
DeclareParams/ParamBuilder 声明字段,运行时反射读写; Inspector 按类型生成控件,SceneSerializer 直通存取; 动画系统接入参数化裁剪;新增 OrbitMove 示例脚本
This commit is contained in:
@@ -131,10 +131,11 @@ namespace Engine::Animation {
|
||||
std::vector<glm::mat4>& outGlobals)
|
||||
{
|
||||
// crossfade 双层 = N 层混合的特例(Direct 模式与 Controller
|
||||
// 过渡共用同一套加权数学)。src 层权重 = 1 - wCur 与历史
|
||||
// mix(src, cur, wCur) 语义等价
|
||||
// 过渡共用同一套加权数学)。两权重和恒为 1,渐进归一化后精确
|
||||
// 等价 mix(src, cur, wCur):起点全 src、终点全 cur,全程平滑
|
||||
//(cur 恒 1.0 的话起点就瞬间跳 50%,fade 有效时长减半——割裂感)
|
||||
PoseLayer layers[2];
|
||||
layers[0] = {&cur, curTime, 1.0f};
|
||||
layers[0] = {&cur, curTime, xf.wCur};
|
||||
u32 count = 1;
|
||||
if (xf.src != nullptr) {
|
||||
layers[1] = {xf.src, xf.srcTime, 1.0f - xf.wCur};
|
||||
|
||||
@@ -20,8 +20,40 @@
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Engine::Animation {
|
||||
// ---- AnimationComponent::Play(按名) 实现(声明在 Components.h)----
|
||||
// 住本 TU:需要 Model 完整类型做 clip 表查找,组件头保持零资产依赖
|
||||
//(Model.h 拖 vulkan/assimp)。定义放全局全限定(类所属命名空间的
|
||||
// 外层才允许成员函数定义)
|
||||
bool Engine::ECS::AnimationComponent::Play(const std::string& clipName,
|
||||
f32 fadeSeconds) {
|
||||
if (!runtimeModel) {
|
||||
return false; // 模型未就绪(首帧前/无渲染组件/换模型间隙)
|
||||
}
|
||||
const auto& clips = runtimeModel->GetClips();
|
||||
for (u32 i = 0; i < clips.size(); ++i) {
|
||||
// 同 clip = 确保在播即可(播放头不动,防下方相位覆写跳帧)
|
||||
if (clips[i].name == clipName) {
|
||||
if (static_cast<int>(i) == clipIndex) {
|
||||
playing = true;
|
||||
return true;
|
||||
}
|
||||
// 相位对齐(Unity 状态机过渡的默认语义:按归一化时间同
|
||||
// 步)——不消毒的话 walk 播到左脚在前、run 却从 0(右脚
|
||||
// 在前)起步,混合的两脚错相,fade 再长也割裂。按下标
|
||||
// Play 不同步(编辑器预览要从头看)
|
||||
const AnimationClip& src = clips[clipIndex];
|
||||
const f32 norm = src.duration > 0.0f
|
||||
? glm::mod(time, src.duration) / src.duration
|
||||
: 0.0f;
|
||||
Play(static_cast<int>(i), fadeSeconds); // 内部 time 归 0
|
||||
time = clips[i].duration * norm; // 覆写为同相位起步
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // 名字未命中(是否告警由调用侧决定)
|
||||
}
|
||||
|
||||
namespace Engine::Animation {
|
||||
void AnimationSystem::Update(ECS::Scene& scene, Assets::AssetManager& assets,
|
||||
f32 dt, FrameSceneData& frame)
|
||||
{
|
||||
@@ -76,10 +108,11 @@ namespace Engine::Animation {
|
||||
ECS::AnimationComponent& anim) {
|
||||
if (anim.mode != ECS::AnimationComponent::AnimMode::Direct)
|
||||
return; // Controller 实体走段 2
|
||||
if (!anim.active) return; // 组件禁用 = 暂停采样(播放头不动)
|
||||
if (!mr.model.Valid()) return; // 未指派/手清 = 无效句柄(Get 对无效 FATAL)
|
||||
const Model* model = assets.GetModel(mr.model);
|
||||
if (!model || !model->HasAnimations()) return;
|
||||
anim.runtimeModel = model; // 按名播放反指(暂停态也刷新)
|
||||
if (!anim.active) return; // 组件禁用 = 暂停采样(播放头不动)
|
||||
const auto& clips = model->GetClips();
|
||||
|
||||
// clip 下标合法性(模型资产可能已换)
|
||||
@@ -144,10 +177,11 @@ namespace Engine::Animation {
|
||||
ECS::AnimationComponent::AnimMode::Controller) {
|
||||
return;
|
||||
}
|
||||
if (!anim.active) return;
|
||||
if (!mr.model.Valid()) return;
|
||||
const Model* model = assets.GetModel(mr.model);
|
||||
if (!model || !model->HasAnimations()) return;
|
||||
anim.runtimeModel = model; // 按名播放反指(暂停态也刷新)
|
||||
if (!anim.active) return;
|
||||
const auto* ctrl = assets.FindController(anim.controllerPath);
|
||||
if (!ctrl) return; // 未加载/路径无效 → rest 兜底
|
||||
const auto& states = ctrl->GetStates();
|
||||
@@ -173,6 +207,18 @@ namespace Engine::Animation {
|
||||
}
|
||||
}
|
||||
|
||||
// 阻尼参数推进(帧率无关指数平滑):current 逐帧逼近
|
||||
// target,写回 floatParams 供条件评估统一读取;收敛贴齐
|
||||
// 消除渐近尾噪(AnimSetFloatDamped 的引擎侧推进端)
|
||||
for (auto& [dname, d] : anim.dampedParams) {
|
||||
const float k = 1.0f - std::exp(-d.response * dt);
|
||||
d.current += (d.target - d.current) * k;
|
||||
if (std::abs(d.target - d.current) < 1e-4f) {
|
||||
d.current = d.target;
|
||||
}
|
||||
anim.floatParams[dname] = d.current;
|
||||
}
|
||||
|
||||
// clip 名 → 下标表
|
||||
auto& lookup = clipLookup[model];
|
||||
if (lookup.empty()) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <Engine/Scripting/ScriptRuntime.h>
|
||||
|
||||
#include <Engine/Core/Application.h>
|
||||
#include <Engine/Core/Log.h>
|
||||
#include <Engine/ECS/Components.h>
|
||||
|
||||
@@ -18,6 +19,28 @@ namespace Engine {
|
||||
return *m_App;
|
||||
}
|
||||
|
||||
// ---- 参数按名读写(ScriptBehavior 声明,经绑定表;写穿
|
||||
// Item.params 由运行时 SetParam 完成)----
|
||||
bool ScriptBehavior::WriteParam(const std::string& name,
|
||||
const ParamValue& v) {
|
||||
for (const auto& b : m_ParamBinds) {
|
||||
if (b.name == name) {
|
||||
return ParamBuilder::WriteBind(b, v);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScriptBehavior::ReadParam(const std::string& name,
|
||||
ParamValue& out) const {
|
||||
for (const auto& b : m_ParamBinds) {
|
||||
if (b.name == name) {
|
||||
return ParamBuilder::ReadBind(b, out);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScriptRuntime::Update(f32 dt) {
|
||||
Sync();
|
||||
|
||||
@@ -26,40 +49,73 @@ namespace Engine {
|
||||
const auto e = static_cast<entt::entity>(bits);
|
||||
const ECS::ScriptComponent* sc = reg.valid(e)
|
||||
? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
|
||||
if (!sc) {
|
||||
continue; // 延迟销毁队列里,下帧 Sync 清理
|
||||
}
|
||||
if (!sc->active) {
|
||||
continue; // 组件总开关:全部实例停 tick(Unity disable)
|
||||
}
|
||||
for (auto& inst : list) {
|
||||
if (!inst.behavior) {
|
||||
continue;
|
||||
}
|
||||
// per-script 启停:按名查项(对账保证存在;新项要
|
||||
// 下帧 Sync 才有实例,这里查不到就跳过)
|
||||
const auto item = std::find_if(
|
||||
sc->items.begin(), sc->items.end(),
|
||||
[&](const ECS::ScriptComponent::Item& it) {
|
||||
return it.name == inst.behavior->Name();
|
||||
});
|
||||
if (item == sc->items.end() || !item->active) {
|
||||
// 启停翻转检测:wantOn = 组件总开关 && 项开关(组件
|
||||
// 关闭时零查找;项按名查找沿用每帧既有的那一次)。
|
||||
// 与上帧 inst.enabled 比较出沿:true→OnEnable,
|
||||
// false→OnDisable(Unity enabled 翻转语义)
|
||||
bool wantOn = false;
|
||||
if (sc && sc->active) {
|
||||
const auto item = std::find_if(
|
||||
sc->items.begin(), sc->items.end(),
|
||||
[&](const ECS::ScriptComponent::Item& it) {
|
||||
return it.name == inst.behavior->Name();
|
||||
});
|
||||
wantOn = item != sc->items.end() && item->active;
|
||||
}
|
||||
if (!wantOn) {
|
||||
if (inst.enabled) {
|
||||
inst.enabled = false;
|
||||
inst.behavior->OnDisable();
|
||||
}
|
||||
continue; // 禁用 = 实例保留停 OnUpdate
|
||||
}
|
||||
if (!inst.created) {
|
||||
inst.behavior->OnCreate();
|
||||
inst.created = true;
|
||||
}
|
||||
if (!inst.enabled) {
|
||||
inst.enabled = true;
|
||||
inst.behavior->OnEnable();
|
||||
}
|
||||
if (!inst.started) {
|
||||
inst.started = true;
|
||||
inst.behavior->OnStart(); // 首次 OnUpdate 前一次
|
||||
}
|
||||
inst.behavior->OnUpdate(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 终局(GameFlow →Stopped 广播):全实例 OnDisable →
|
||||
// OnDestroy。与 Sync 的孤儿清理同序,范围 = 全部且不重建;
|
||||
// Application 的监听先于编辑器回滚注册(组合根构造序),
|
||||
// OnDestroy 里读到的仍是上局实体 ----
|
||||
void ScriptRuntime::EndPlay() {
|
||||
for (auto& [bits, list] : m_Instances) {
|
||||
for (auto& inst : list) {
|
||||
if (!inst.behavior) {
|
||||
continue;
|
||||
}
|
||||
if (inst.enabled) {
|
||||
inst.enabled = false;
|
||||
inst.behavior->OnDisable();
|
||||
}
|
||||
inst.behavior->OnDestroy();
|
||||
}
|
||||
}
|
||||
m_Instances.clear();
|
||||
}
|
||||
|
||||
void ScriptRuntime::Sync() {
|
||||
auto& reg = m_Scene.GetRegistry();
|
||||
|
||||
// 1. 孤儿/改名清理:实体或组件没了 → 整组 OnDestroy;
|
||||
// 实例名不在 items 里(改名/移除)→ 单个 OnDestroy
|
||||
// 实例名不在 items 里(改名/移除)→ 单个 OnDestroy。
|
||||
// 销毁次序 = OnDisable(守卫防双发)→ OnDestroy
|
||||
for (auto it = m_Instances.begin(); it != m_Instances.end();) {
|
||||
const auto e = static_cast<entt::entity>(it->first);
|
||||
const ECS::ScriptComponent* sc =
|
||||
@@ -67,9 +123,14 @@ namespace Engine {
|
||||
: nullptr;
|
||||
if (!sc) {
|
||||
for (auto& inst : it->second) {
|
||||
if (inst.behavior) {
|
||||
inst.behavior->OnDestroy();
|
||||
if (!inst.behavior) {
|
||||
continue;
|
||||
}
|
||||
if (inst.enabled) {
|
||||
inst.enabled = false;
|
||||
inst.behavior->OnDisable();
|
||||
}
|
||||
inst.behavior->OnDestroy();
|
||||
}
|
||||
it = m_Instances.erase(it);
|
||||
continue;
|
||||
@@ -86,6 +147,10 @@ namespace Engine {
|
||||
continue;
|
||||
}
|
||||
if (li->behavior) {
|
||||
if (li->enabled) {
|
||||
li->enabled = false;
|
||||
li->behavior->OnDisable();
|
||||
}
|
||||
li->behavior->OnDestroy();
|
||||
}
|
||||
li = list.erase(li);
|
||||
@@ -98,7 +163,9 @@ namespace Engine {
|
||||
}
|
||||
|
||||
// 2. 缺失实例化:逐项按名查注册表(未知名警告——场景含未注册
|
||||
// 脚本时弱前向兼容,跳过不影响其余脚本)
|
||||
// 脚本时弱前向兼容,跳过不影响其余脚本)。灌值次序对齐
|
||||
// Unity:声明(成员=声明默认)→ 组件存档覆盖 → 收值回调
|
||||
// → OnCreate(= Awake,拿到最终参数值)
|
||||
const auto view = reg.view<ECS::ScriptComponent>();
|
||||
for (auto [e, sc] : view.each()) {
|
||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||
@@ -124,6 +191,12 @@ namespace Engine {
|
||||
}
|
||||
behavior->Bind(m_Scene, e, m_App);
|
||||
|
||||
ParamBuilder pb;
|
||||
behavior->DeclareParams(pb);
|
||||
pb.Apply(item.params);
|
||||
behavior->SetParamBinds(pb.TakeBinds());
|
||||
behavior->OnParamsApplied();
|
||||
|
||||
ScriptInstance inst;
|
||||
inst.behavior = std::move(behavior);
|
||||
inst.behavior->OnCreate(); // Unity Awake 语义:实例化即回调
|
||||
@@ -133,5 +206,98 @@ namespace Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 参数热刷新(编辑期一次性):重新声明 + 按存档灌值 ----
|
||||
void ScriptRuntime::RefreshParams(u32 entityBits) {
|
||||
const auto it = m_Instances.find(entityBits);
|
||||
if (it == m_Instances.end()) {
|
||||
return;
|
||||
}
|
||||
auto& reg = m_Scene.GetRegistry();
|
||||
const auto e = static_cast<entt::entity>(entityBits);
|
||||
const ECS::ScriptComponent* sc = reg.valid(e)
|
||||
? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
|
||||
if (!sc) {
|
||||
return;
|
||||
}
|
||||
for (auto& inst : it->second) {
|
||||
if (!inst.behavior) {
|
||||
continue;
|
||||
}
|
||||
ParamBuilder pb;
|
||||
inst.behavior->DeclareParams(pb);
|
||||
const auto item = std::find_if(
|
||||
sc->items.begin(), sc->items.end(),
|
||||
[&](const ECS::ScriptComponent::Item& it2) {
|
||||
return it2.name == inst.behavior->Name();
|
||||
});
|
||||
if (item != sc->items.end()) {
|
||||
pb.Apply(item->params);
|
||||
}
|
||||
inst.behavior->SetParamBinds(pb.TakeBinds());
|
||||
inst.behavior->OnParamsApplied();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 跨脚本按名参数访问 ----
|
||||
bool ScriptRuntime::SetParam(u32 entityBits,
|
||||
const std::string& scriptName,
|
||||
const std::string& param,
|
||||
const ParamValue& value) {
|
||||
auto& reg = m_Scene.GetRegistry();
|
||||
const auto e = static_cast<entt::entity>(entityBits);
|
||||
ECS::ScriptComponent* sc = reg.valid(e)
|
||||
? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
|
||||
if (!sc) {
|
||||
return false;
|
||||
}
|
||||
const auto item = std::find_if(
|
||||
sc->items.begin(), sc->items.end(),
|
||||
[&](const ECS::ScriptComponent::Item& it) {
|
||||
return it.name == scriptName;
|
||||
});
|
||||
if (item == sc->items.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 目标实例成员直写(实例未建/未声明则跳过,存档仍写穿)
|
||||
const auto ii = m_Instances.find(entityBits);
|
||||
if (ii != m_Instances.end()) {
|
||||
for (auto& inst : ii->second) {
|
||||
if (inst.behavior &&
|
||||
inst.behavior->Name() == scriptName) {
|
||||
inst.behavior->WriteParam(param, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写穿 Item.params:首次写新名一次分配,此后零分配
|
||||
const auto pit = std::find_if(
|
||||
item->params.begin(), item->params.end(),
|
||||
[&](const ScriptParam& p) { return p.name == param; });
|
||||
if (pit != item->params.end()) {
|
||||
pit->value = value;
|
||||
} else {
|
||||
item->params.push_back({param, value});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScriptRuntime::GetParam(u32 entityBits,
|
||||
const std::string& scriptName,
|
||||
const std::string& param,
|
||||
ParamValue& out) const {
|
||||
const auto ii = m_Instances.find(entityBits);
|
||||
if (ii == m_Instances.end()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& inst : ii->second) {
|
||||
if (inst.behavior && inst.behavior->Name() == scriptName) {
|
||||
return inst.behavior->ReadParam(param, out);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Scripting
|
||||
} // namespace Engine
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user