新增脚本参数系统:声明式参数 + Inspector 自动控件 + 场景序列化
DeclareParams/ParamBuilder 声明字段,运行时反射读写; Inspector 按类型生成控件,SceneSerializer 直通存取; 动画系统接入参数化裁剪;新增 OrbitMove 示例脚本
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
#include "Engine/Renderer/MaterialAsset.h" // 材质覆盖槽(.material 资产)
|
#include "Engine/Renderer/MaterialAsset.h" // 材质覆盖槽(.material 资产)
|
||||||
#include "Engine/Physics/PhysicsWorld.h" // Apply Impulse 调试(GetPhysics 门面)
|
#include "Engine/Physics/PhysicsWorld.h" // Apply Impulse 调试(GetPhysics 门面)
|
||||||
#include "Engine/Scripting/Script.h" // 脚本下拉(ScriptRegistry::Names)
|
#include "Engine/Scripting/Script.h" // 脚本下拉(ScriptRegistry::Names)
|
||||||
|
#include "Engine/Scripting/ScriptRuntime.h" // 参数编辑 → RefreshParams
|
||||||
#include "Engine/Core/Log.h" // Create Override 失败告警
|
#include "Engine/Core/Log.h" // Create Override 失败告警
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
@@ -38,6 +39,28 @@ namespace
|
|||||||
// 材质槽循环索引用(u32 定义于 namespace Engine,App 层显式引入)
|
// 材质槽循环索引用(u32 定义于 namespace Engine,App 层显式引入)
|
||||||
using Engine::u32;
|
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 槽共用;
|
// 递归收集 assets/ 下模型容器文件(模型槽与动画 attach 槽共用;
|
||||||
// 无法从扩展名区分"模型 fbx"与"纯动画 fbx",一并列出)。
|
// 无法从扩展名区分"模型 fbx"与"纯动画 fbx",一并列出)。
|
||||||
// 会话级缓存:目录条目几十量级,避免每帧递归扫描
|
// 会话级缓存:目录条目几十量级,避免每帧递归扫描
|
||||||
@@ -1156,6 +1179,7 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
ImGui::SeparatorText("Movement");
|
ImGui::SeparatorText("Movement");
|
||||||
ImGui::DragFloat("Max Slope (deg)", &cc->maxSlopeAngle, 1.0f, 1.0f, 89.0f);
|
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("Step Height", &cc->stepHeight, 0.02f, 0.0f, 2.0f);
|
||||||
|
ImGui::DragFloat("Gravity Scale", &cc->gravityScale, 0.1f, 0.0f, 10.0f);
|
||||||
|
|
||||||
// ---- Debug:遥测(只读,运行时状态在 PhysicsWorld)----
|
// ---- Debug:遥测(只读,运行时状态在 PhysicsWorld)----
|
||||||
if (auto& phy = ctx.app->GetPhysics(); phy.IsEnabled())
|
if (auto& phy = ctx.app->GetPhysics(); phy.IsEnabled())
|
||||||
@@ -1249,6 +1273,102 @@ void InspectorPanel::Draw(const EditorContext& ctx, entt::entity selected)
|
|||||||
{
|
{
|
||||||
removeIdx = i;
|
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();
|
ImGui::PopID();
|
||||||
// 已填名但注册表查无此类:场景加载早于脚本注册/改名都会走到这
|
// 已填名但注册表查无此类:场景加载早于脚本注册/改名都会走到这
|
||||||
if (!item.name.empty() && cur == 0)
|
if (!item.name.empty() && cur == 0)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
//
|
//
|
||||||
// Spinner.cpp:C++ 原生脚本示范——Y 轴匀速自转。
|
// Spinner.cpp:C++ 原生脚本示范——Y 轴匀速自转。
|
||||||
//
|
//
|
||||||
// 写脚本三步:
|
// 写脚本四步:
|
||||||
// 1. 新建 .cpp 放本目录(CMake 自动进构建)
|
// 1. 新建 .cpp 放本目录(CMake 自动进构建)
|
||||||
// 2. 继承 ScriptBehavior,覆写 Name/OnCreate/OnUpdate/OnDestroy
|
// 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:
|
public:
|
||||||
const char* Name() const override { return "Spinner"; }
|
const char* Name() const override { return "Spinner"; }
|
||||||
|
|
||||||
|
void DeclareParams(Engine::Scripting::ParamBuilder& pb) override {
|
||||||
|
pb.Field("Speed", &m_Speed, 90.0f); // 度/秒
|
||||||
|
}
|
||||||
|
|
||||||
void OnCreate() override {
|
void OnCreate() override {
|
||||||
// 实体与组件已就位;需要别的组件时用 Get<T>()(不存在会炸)
|
// 实体与组件已就位;需要别的组件时用 Get<T>()(不存在会炸)
|
||||||
}
|
}
|
||||||
@@ -31,7 +37,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float m_Speed = 90.0f; // 度/秒
|
float m_Speed = 90.0f; // 度/秒(声明默认;实例化期被存档值覆盖)
|
||||||
};
|
};
|
||||||
|
|
||||||
ENGINE_REGISTER_SCRIPT(Spinner);
|
ENGINE_REGISTER_SCRIPT(Spinner);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <Engine/Core/UUID.h>
|
#include <Engine/Core/UUID.h>
|
||||||
#include <Engine/Core/AssetRef.h>
|
#include <Engine/Core/AssetRef.h>
|
||||||
#include <Engine/Renderer/Camera.h>
|
#include <Engine/Renderer/Camera.h>
|
||||||
|
#include <Engine/Scripting/ScriptParam.h>
|
||||||
|
|
||||||
#include <entt/entt.hpp>
|
#include <entt/entt.hpp>
|
||||||
|
|
||||||
@@ -233,6 +234,10 @@ namespace Engine::ECS {
|
|||||||
f32 fadeElapsed = 0.0f; // 已过渡时长
|
f32 fadeElapsed = 0.0f; // 已过渡时长
|
||||||
f32 fadeDuration = 0.3f; // 过渡总时长(秒)
|
f32 fadeDuration = 0.3f; // 过渡总时长(秒)
|
||||||
|
|
||||||
|
// 按名播放的运行时反指(瞬态,不落盘不序列化):AnimationSystem
|
||||||
|
// 每帧回填(同 RigidBody::bodyValid 模式)。空 = 模型未就绪
|
||||||
|
const Model* runtimeModel = nullptr;
|
||||||
|
|
||||||
// ---- 资产引用(序列化落盘)----
|
// ---- 资产引用(序列化落盘)----
|
||||||
// 外部动画资产(编辑器 attach 的动画容器文件,随场景持久化)。
|
// 外部动画资产(编辑器 attach 的动画容器文件,随场景持久化)。
|
||||||
// clips 本体运行时并入 Model 资产(共享);重启后由 Inspector
|
// clips 本体运行时并入 Model 资产(共享);重启后由 Inspector
|
||||||
@@ -253,6 +258,16 @@ namespace Engine::ECS {
|
|||||||
std::unordered_map<std::string, bool> boolParams;
|
std::unordered_map<std::string, bool> boolParams;
|
||||||
std::vector<std::string> pendingTriggers; // 消费即清(瞬态)
|
std::vector<std::string> pendingTriggers; // 消费即清(瞬态)
|
||||||
|
|
||||||
|
// 阻尼参数推进状态(瞬态,不落盘——重启从 floatParams 当前值
|
||||||
|
// 重新起步):current 逐帧指数逼近 target,写回 floatParams 供
|
||||||
|
// 状态机条件统一读取
|
||||||
|
struct DampedParam {
|
||||||
|
float current = 0.0f;
|
||||||
|
float target = 0.0f;
|
||||||
|
float response = 8.0f; // 逼近速率(1/s,越大越快)
|
||||||
|
};
|
||||||
|
std::unordered_map<std::string, DampedParam> dampedParams;
|
||||||
|
|
||||||
// 状态机瞬态(不落盘,重启从 entry state 开始)
|
// 状态机瞬态(不落盘,重启从 entry state 开始)
|
||||||
std::string currentState; // 空 = 未初始化(System 首帧置 entry)
|
std::string currentState; // 空 = 未初始化(System 首帧置 entry)
|
||||||
f32 stateTime = 0.0f; // 当前状态播放头(秒)
|
f32 stateTime = 0.0f; // 当前状态播放头(秒)
|
||||||
@@ -266,6 +281,22 @@ namespace Engine::ECS {
|
|||||||
{
|
{
|
||||||
floatParams[name] = value;
|
floatParams[name] = value;
|
||||||
}
|
}
|
||||||
|
// 阻尼变体(Unity SetFloat(name, target, dampTime) 同构):登记
|
||||||
|
// 目标值,AnimationSystem Controller 驱动期逐帧指数逼近(帧率
|
||||||
|
// 无关)。首次登记从 floatParams 当前值起步(无则取目标),
|
||||||
|
// 避免跳变。与 SetFloat 同名混用 = 直接 set 会被下一帧阻尼值
|
||||||
|
// 覆盖,别混
|
||||||
|
void SetFloatDamped(const std::string& name, float target, float response)
|
||||||
|
{
|
||||||
|
auto [it, inserted] = dampedParams.try_emplace(name);
|
||||||
|
it->second.target = target;
|
||||||
|
it->second.response = glm::max(response, 0.01f);
|
||||||
|
if (inserted) {
|
||||||
|
const auto fp = floatParams.find(name);
|
||||||
|
it->second.current =
|
||||||
|
fp != floatParams.end() ? fp->second : target;
|
||||||
|
}
|
||||||
|
}
|
||||||
void SetBool(const std::string& name, bool value)
|
void SetBool(const std::string& name, bool value)
|
||||||
{
|
{
|
||||||
boolParams[name] = value;
|
boolParams[name] = value;
|
||||||
@@ -309,6 +340,13 @@ namespace Engine::ECS {
|
|||||||
time = 0.0f;
|
time = 0.0f;
|
||||||
playing = true;
|
playing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按名播放(Unity Animator.Play("Walk") 同形,推荐):clip 名经
|
||||||
|
// runtimeModel 的 clip 表解析,消掉下标硬耦合(attach 顺序变了
|
||||||
|
// 脚本不改)。未命中/模型未就绪返回 false。实现住
|
||||||
|
// AnimationSystem.cpp——需要 Model 完整类型,组件头保持零资产
|
||||||
|
// 依赖(Model.h 拖 vulkan/assimp,进本头会污染全部包含者)
|
||||||
|
bool Play(const std::string& clipName, f32 fadeSeconds = 0.3f);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
||||||
@@ -430,6 +468,9 @@ namespace Engine::ECS {
|
|||||||
f32 mass = 70.0f;
|
f32 mass = 70.0f;
|
||||||
f32 maxSlopeAngle = 45.0f; // 度:可站立的最大坡角
|
f32 maxSlopeAngle = 45.0f; // 度:可站立的最大坡角
|
||||||
f32 stepHeight = 0.4f; // 台阶自动攀爬高(WalkStairs)
|
f32 stepHeight = 0.4f; // 台阶自动攀爬高(WalkStairs)
|
||||||
|
// 重力倍率(UE CharacterMovement.GravityScale 同位):真实 9.81
|
||||||
|
// 对人形角色偏"棉花糖"(滞空 1.2s+),游戏手感普遍 2~3 倍
|
||||||
|
f32 gravityScale = 2.0f;
|
||||||
|
|
||||||
bool operator==(const CharacterControllerComponent&) const = default;
|
bool operator==(const CharacterControllerComponent&) const = default;
|
||||||
// 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯
|
// 实体原点 = 脚底(胶囊底端),符合角色 pivot 习惯
|
||||||
@@ -444,6 +485,11 @@ namespace Engine::ECS {
|
|||||||
struct Item {
|
struct Item {
|
||||||
std::string name;
|
std::string name;
|
||||||
bool active = true; // 禁用 = 实例保留但停 OnUpdate
|
bool active = true; // 禁用 = 实例保留但停 OnUpdate
|
||||||
|
// 参数(权威数据,随场景序列化)。脚本成员 = 运行时真值,
|
||||||
|
// 实例化期由 ScriptRuntime 按 schema 灌入:键缺失 → 声明
|
||||||
|
// 默认,多余键忽略;运行时 SetParam 写穿回这里(PIE 实时
|
||||||
|
// 反映,Stop 由快照天然回滚)
|
||||||
|
std::vector<Engine::Scripting::ScriptParam> params;
|
||||||
|
|
||||||
bool operator==(const Item&) const = default;
|
bool operator==(const Item&) const = default;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,13 @@
|
|||||||
// 基类接口——将来接 Lua/C# 时各实现一种 foreign behavior(内部持
|
// 基类接口——将来接 Lua/C# 时各实现一种 foreign behavior(内部持
|
||||||
// VM 状态),引擎零改动。
|
// VM 状态),引擎零改动。
|
||||||
//
|
//
|
||||||
|
// 瘦基类(对齐 Unity6/UE5 现役形态):基类只供生命周期 + 参数 +
|
||||||
|
// 实体访问器,零领域 API——物理住 PhysicsWorld(App().GetPhysics()
|
||||||
|
// 直调),动画住 AnimationComponent(GetComponent 直调,Animator
|
||||||
|
// 同形)。DeclareParams = C# 反射缺失下 UPROPERTY 的等价物:Unity
|
||||||
|
// 里你从不写声明是因为语言替你做了,C++ 里每个引擎都得补这一环
|
||||||
|
//(UE 让你标 UPROPERTY,这里让你写一行 Declare)。
|
||||||
|
//
|
||||||
// 组件只存 scriptName 字符串(POD,序列化天然支持:Lua/C# 同样按名
|
// 组件只存 scriptName 字符串(POD,序列化天然支持:Lua/C# 同样按名
|
||||||
// 解析);实例由 ScriptRuntime 对账式管理(照 PhysicsWorld 模式),
|
// 解析);实例由 ScriptRuntime 对账式管理(照 PhysicsWorld 模式),
|
||||||
// 不进组件——组件可拷贝/可比较。
|
// 不进组件——组件可拷贝/可比较。
|
||||||
@@ -21,10 +28,12 @@
|
|||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <Engine/ECS/Components.h>
|
#include <Engine/ECS/Components.h>
|
||||||
#include <Engine/ECS/Scene.h>
|
#include <Engine/ECS/Scene.h>
|
||||||
|
#include <Engine/Scripting/ScriptParam.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
@@ -39,11 +48,27 @@ namespace Engine {
|
|||||||
// 注册表键(= 组件 scriptName),须与 ENGINE_REGISTER_SCRIPT 名一致
|
// 注册表键(= 组件 scriptName),须与 ENGINE_REGISTER_SCRIPT 名一致
|
||||||
virtual const char* Name() const = 0;
|
virtual const char* Name() const = 0;
|
||||||
|
|
||||||
// 生命周期(吃 scaled dt:编辑器暂停 timeScale=0 时自然停摆)
|
// 生命周期(吃 scaled dt:编辑器暂停 timeScale=0 时自然停摆)。
|
||||||
|
// 顺序对齐 Unity:OnCreate(= Awake,实例化即调)→ OnEnable
|
||||||
|
// → OnStart(首次 OnUpdate 前一次)→ OnUpdate* → OnDisable →
|
||||||
|
// OnDestroy(可能因实体销毁触发,句柄已失效只做自身清理)。
|
||||||
|
// 跨实体 OnEnable 顺序不保证(实例表无序),与 Unity 一致
|
||||||
virtual void OnCreate() {}
|
virtual void OnCreate() {}
|
||||||
|
virtual void OnEnable() {}
|
||||||
|
virtual void OnStart() {}
|
||||||
virtual void OnUpdate(f32 dt) {}
|
virtual void OnUpdate(f32 dt) {}
|
||||||
|
virtual void OnDisable() {}
|
||||||
virtual void OnDestroy() {}
|
virtual void OnDestroy() {}
|
||||||
|
|
||||||
|
// 参数声明(实例化灌值与注册表 schema 缓存共用入口;外语绑定
|
||||||
|
// 同键接入)。契约:只写成员指针与常量,禁触场景/应用——
|
||||||
|
// schema 缓存路径用未 Bind 的临时实例调用
|
||||||
|
virtual void DeclareParams(ParamBuilder& pb) { (void)pb; }
|
||||||
|
|
||||||
|
// 参数灌值后回调(外语侧收值接缝:C++ 成员已直写,VM 绑定
|
||||||
|
// 在此按名取值)
|
||||||
|
virtual void OnParamsApplied() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// 便捷访问:绑定实体的 TransformComponent
|
// 便捷访问:绑定实体的 TransformComponent
|
||||||
ECS::TransformComponent& Transform() {
|
ECS::TransformComponent& Transform() {
|
||||||
@@ -60,7 +85,8 @@ namespace Engine {
|
|||||||
return m_Scene->GetRegistry().try_get<T>(m_Entity);
|
return m_Scene->GetRegistry().try_get<T>(m_Entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绑定实体的 entityBits(物理门面按键值寻址)
|
// 绑定实体的 entityBits(PhysicsWorld 按此键寻址:直调
|
||||||
|
// App().GetPhysics() 的各 API 时作第一个参数)
|
||||||
u32 EntityBits() const {
|
u32 EntityBits() const {
|
||||||
return static_cast<u32>(entt::to_integral(m_Entity));
|
return static_cast<u32>(entt::to_integral(m_Entity));
|
||||||
}
|
}
|
||||||
@@ -70,6 +96,66 @@ namespace Engine {
|
|||||||
|
|
||||||
bool Valid() const { return m_Scene != nullptr && m_Entity != entt::null; }
|
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:
|
private:
|
||||||
friend class ScriptRuntime;
|
friend class ScriptRuntime;
|
||||||
void Bind(ECS::Scene& scene, entt::entity e, Application* app) {
|
void Bind(ECS::Scene& scene, entt::entity e, Application* app) {
|
||||||
@@ -77,10 +163,19 @@ namespace Engine {
|
|||||||
m_Entity = e;
|
m_Entity = e;
|
||||||
m_App = app;
|
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;
|
ECS::Scene* m_Scene = nullptr;
|
||||||
entt::entity m_Entity = entt::null;
|
entt::entity m_Entity = entt::null;
|
||||||
Application* m_App = nullptr;
|
Application* m_App = nullptr;
|
||||||
|
std::vector<ParamBuilder::Bind> m_ParamBinds;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- 按名注册表:脚本类文件级自注册,运行时按名实例化 ----
|
// ---- 按名注册表:脚本类文件级自注册,运行时按名实例化 ----
|
||||||
@@ -108,6 +203,28 @@ namespace Engine {
|
|||||||
return 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:
|
private:
|
||||||
static std::unordered_map<std::string, Factory>& Map() {
|
static std::unordered_map<std::string, Factory>& Map() {
|
||||||
static std::unordered_map<std::string, Factory> m;
|
static std::unordered_map<std::string, Factory> m;
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
//
|
||||||
|
// ScriptParam.h:脚本参数——按名变体值 + 声明式 schema 构建器。
|
||||||
|
//
|
||||||
|
// 设计本质(UE/Unity 共识):参数数据按名存在序列化层(组件
|
||||||
|
// ScriptComponent::Item.params,随场景持久化),C++ 脚本成员只是运行时
|
||||||
|
// 真值缓存——这条边界正是将来 Lua/C# 外语绑定的接缝:
|
||||||
|
// - C++ 脚本:DeclareParams 里 Field(name, &成员, 默认值) 双写声明
|
||||||
|
// (灌值直写成员,零回收代码);
|
||||||
|
// - 外语脚本:Declare(name, 类型, 默认值) 无指针声明,值经
|
||||||
|
// OnParamsApplied 回收(VM 侧按名取)。
|
||||||
|
//
|
||||||
|
// schema 由注册表按脚本类名惰性缓存(ScriptRegistry::Schema)——描述
|
||||||
|
// 里不含指针,缓存无悬垂;成员指针(Bind)只随实例存亡,不进缓存。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SCRIPT_PARAM_H
|
||||||
|
#define HELLOVULKAN_SCRIPT_PARAM_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
namespace Scripting {
|
||||||
|
|
||||||
|
// 参数类型(序列化分支 + Inspector 控件选择)。枚举序 = variant
|
||||||
|
// 臂序(ParamValue::Type() 直转);新类型 = 枚举尾插 + 追加臂,
|
||||||
|
// 旧档位前向兼容
|
||||||
|
enum class ParamType : u8 { F32, I32, Bool, String, Vec2, Vec3 };
|
||||||
|
|
||||||
|
// 类型 → 枚举映射(Field 重载与消费端共用)
|
||||||
|
template<typename T> struct ParamTypeOf;
|
||||||
|
template<> struct ParamTypeOf<f32> { static constexpr ParamType value = ParamType::F32; };
|
||||||
|
template<> struct ParamTypeOf<i32> { static constexpr ParamType value = ParamType::I32; };
|
||||||
|
template<> struct ParamTypeOf<bool> { static constexpr ParamType value = ParamType::Bool; };
|
||||||
|
template<> struct ParamTypeOf<std::string> { static constexpr ParamType value = ParamType::String; };
|
||||||
|
template<> struct ParamTypeOf<glm::vec2> { static constexpr ParamType value = ParamType::Vec2; };
|
||||||
|
template<> struct ParamTypeOf<glm::vec3> { static constexpr ParamType value = ParamType::Vec3; };
|
||||||
|
|
||||||
|
// 单参数的存储形态(类型标签由 variant 臂序自持)
|
||||||
|
struct ParamValue {
|
||||||
|
std::variant<f32, i32, bool, std::string, glm::vec2, glm::vec3>
|
||||||
|
v{f32{0.0f}};
|
||||||
|
|
||||||
|
ParamType Type() const { return static_cast<ParamType>(v.index()); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static constexpr ParamType TypeOf() { return ParamTypeOf<T>::value; }
|
||||||
|
|
||||||
|
bool operator==(const ParamValue&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 单参数(组件 Item.params 的元素,随场景序列化)
|
||||||
|
struct ScriptParam {
|
||||||
|
std::string name;
|
||||||
|
ParamValue value;
|
||||||
|
|
||||||
|
bool operator==(const ScriptParam&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 参数声明构建器:DeclareParams(pb) 里逐项 Field/Declare,产出
|
||||||
|
// 描述列表(schema,持久)+ 绑定表(成员指针,瞬态)。
|
||||||
|
// Field 即把声明默认写进成员——未灌值前成员就是默认值
|
||||||
|
class ParamBuilder {
|
||||||
|
public:
|
||||||
|
// ---- C++ 侧:声明 + 绑定成员(灌值直写)----
|
||||||
|
void Field(std::string_view name, f32* dst, f32 def) {
|
||||||
|
*dst = def;
|
||||||
|
AddDesc(name, ParamType::F32, ParamValue{def});
|
||||||
|
m_Binds.push_back({std::string(name), ParamType::F32, dst});
|
||||||
|
}
|
||||||
|
void Field(std::string_view name, i32* dst, i32 def) {
|
||||||
|
*dst = def;
|
||||||
|
AddDesc(name, ParamType::I32, ParamValue{def});
|
||||||
|
m_Binds.push_back({std::string(name), ParamType::I32, dst});
|
||||||
|
}
|
||||||
|
void Field(std::string_view name, bool* dst, bool def) {
|
||||||
|
*dst = def;
|
||||||
|
AddDesc(name, ParamType::Bool, ParamValue{def});
|
||||||
|
m_Binds.push_back({std::string(name), ParamType::Bool, dst});
|
||||||
|
}
|
||||||
|
void Field(std::string_view name, std::string* dst,
|
||||||
|
const std::string& def) {
|
||||||
|
*dst = def;
|
||||||
|
AddDesc(name, ParamType::String, ParamValue{def});
|
||||||
|
m_Binds.push_back({std::string(name), ParamType::String, dst});
|
||||||
|
}
|
||||||
|
void Field(std::string_view name, glm::vec2* dst,
|
||||||
|
const glm::vec2& def) {
|
||||||
|
*dst = def;
|
||||||
|
AddDesc(name, ParamType::Vec2, ParamValue{def});
|
||||||
|
m_Binds.push_back({std::string(name), ParamType::Vec2, dst});
|
||||||
|
}
|
||||||
|
void Field(std::string_view name, glm::vec3* dst,
|
||||||
|
const glm::vec3& def) {
|
||||||
|
*dst = def;
|
||||||
|
AddDesc(name, ParamType::Vec3, ParamValue{def});
|
||||||
|
m_Binds.push_back({std::string(name), ParamType::Vec3, dst});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 外语侧:仅声明不绑定(值经 OnParamsApplied 回收)----
|
||||||
|
void Declare(std::string_view name, ParamType t, ParamValue def) {
|
||||||
|
AddDesc(name, t, std::move(def));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明描述(持久——进注册表 schema 缓存,无指针无悬垂)
|
||||||
|
struct Desc {
|
||||||
|
std::string name;
|
||||||
|
ParamType type;
|
||||||
|
ParamValue def;
|
||||||
|
};
|
||||||
|
// 成员绑定(瞬态——指针随声明实例存亡,只进 ScriptBehavior)
|
||||||
|
struct Bind {
|
||||||
|
std::string name;
|
||||||
|
ParamType type;
|
||||||
|
void* ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<Desc>& Descs() const { return m_Descs; }
|
||||||
|
std::vector<Bind> TakeBinds() { return std::move(m_Binds); }
|
||||||
|
|
||||||
|
// 单绑定写值:类型不符返回 false(SetParam/跨脚本写共用)
|
||||||
|
static bool WriteBind(const Bind& b, const ParamValue& v) {
|
||||||
|
if (b.type != v.Type()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
switch (b.type) {
|
||||||
|
case ParamType::F32:
|
||||||
|
*static_cast<f32*>(b.ptr) = std::get<f32>(v.v);
|
||||||
|
return true;
|
||||||
|
case ParamType::I32:
|
||||||
|
*static_cast<i32*>(b.ptr) = std::get<i32>(v.v);
|
||||||
|
return true;
|
||||||
|
case ParamType::Bool:
|
||||||
|
*static_cast<bool*>(b.ptr) = std::get<bool>(v.v);
|
||||||
|
return true;
|
||||||
|
case ParamType::String:
|
||||||
|
*static_cast<std::string*>(b.ptr) =
|
||||||
|
std::get<std::string>(v.v);
|
||||||
|
return true;
|
||||||
|
case ParamType::Vec2:
|
||||||
|
*static_cast<glm::vec2*>(b.ptr) = std::get<glm::vec2>(v.v);
|
||||||
|
return true;
|
||||||
|
case ParamType::Vec3:
|
||||||
|
*static_cast<glm::vec3*>(b.ptr) = std::get<glm::vec3>(v.v);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单绑定读值(按绑定类型出值;GetParam/跨脚本读共用)
|
||||||
|
static bool ReadBind(const Bind& b, ParamValue& out) {
|
||||||
|
switch (b.type) {
|
||||||
|
case ParamType::F32:
|
||||||
|
out = ParamValue{*static_cast<f32*>(b.ptr)};
|
||||||
|
return true;
|
||||||
|
case ParamType::I32:
|
||||||
|
out = ParamValue{*static_cast<i32*>(b.ptr)};
|
||||||
|
return true;
|
||||||
|
case ParamType::Bool:
|
||||||
|
out = ParamValue{*static_cast<bool*>(b.ptr)};
|
||||||
|
return true;
|
||||||
|
case ParamType::String:
|
||||||
|
out = ParamValue{*static_cast<std::string*>(b.ptr)};
|
||||||
|
return true;
|
||||||
|
case ParamType::Vec2:
|
||||||
|
out = ParamValue{*static_cast<glm::vec2*>(b.ptr)};
|
||||||
|
return true;
|
||||||
|
case ParamType::Vec3:
|
||||||
|
out = ParamValue{*static_cast<glm::vec3*>(b.ptr)};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按名灌值进绑定成员:命中→直写;未绑定(外语声明)→跳过;
|
||||||
|
// 类型不符/多余键→忽略(读端已校正,这里兜底)。
|
||||||
|
// 缺失键不写——Field 已把声明默认写进成员
|
||||||
|
void Apply(const std::vector<ScriptParam>& params) const {
|
||||||
|
for (const auto& p : params) {
|
||||||
|
const auto b = std::find_if(
|
||||||
|
m_Binds.begin(), m_Binds.end(),
|
||||||
|
[&](const Bind& it) { return it.name == p.name; });
|
||||||
|
if (b != m_Binds.end()) {
|
||||||
|
WriteBind(*b, p.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AddDesc(std::string_view name, ParamType t, ParamValue def) {
|
||||||
|
assert(!name.empty() && "参数名不能为空");
|
||||||
|
assert(std::none_of(m_Descs.begin(), m_Descs.end(),
|
||||||
|
[&](const Desc& d) {
|
||||||
|
return d.name == name;
|
||||||
|
}) &&
|
||||||
|
"参数重名");
|
||||||
|
m_Descs.push_back({std::string(name), t, std::move(def)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Desc> m_Descs;
|
||||||
|
std::vector<Bind> m_Binds;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Scripting
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCRIPT_PARAM_H
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
//
|
//
|
||||||
// ScriptRuntime.h:脚本生命周期驱动。对账式管理(组件项加/删/改名 ↔
|
// ScriptRuntime.h:脚本生命周期驱动。对账式管理(组件项加/删/改名 ↔
|
||||||
// 实例建/毁),照 PhysicsWorld.SyncScene 模式——每帧对账,编辑器操作
|
// 实例建/毁),照 PhysicsWorld.SyncScene 模式——Running 中每帧对账,
|
||||||
// 即时生效,场景加载/实体销毁自动消化。
|
// PIE 中组件增删/改名即时生效,场景加载/实体销毁自动消化。
|
||||||
|
//
|
||||||
|
// Play Mode 语义(Unity/UE PIE 对齐):编辑态不实例化任何脚本——
|
||||||
|
// 实例生命周期 = 一次 PIE 局,Play 后首次 Update 对账重建,Stop 时
|
||||||
|
// EndPlay 统一终局,成员状态不跨局泄漏。
|
||||||
//
|
//
|
||||||
// 多脚本:一个实体可挂任意多个脚本项(ScriptComponent::items),实例
|
// 多脚本:一个实体可挂任意多个脚本项(ScriptComponent::items),实例
|
||||||
// 按名字与 items 对账——重命名/删除走 OnDestroy 清理,未动的实例不重建。
|
// 按名字与 items 对账——重命名/删除走 OnDestroy 清理,未动的实例不重建。
|
||||||
@@ -23,10 +27,13 @@ namespace Engine {
|
|||||||
|
|
||||||
namespace Scripting {
|
namespace Scripting {
|
||||||
|
|
||||||
// 实例簿记:行为对象 + OnCreate 是否已调
|
// 实例簿记:行为对象 + 生命周期位(created = OnCreate 已调;
|
||||||
|
// enabled = 当前启停态,翻转检测基准;started = OnStart 已发)
|
||||||
struct ScriptInstance {
|
struct ScriptInstance {
|
||||||
Scope<ScriptBehavior> behavior;
|
Scope<ScriptBehavior> behavior;
|
||||||
bool created = false;
|
bool created = false;
|
||||||
|
bool enabled = false;
|
||||||
|
bool started = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ScriptRuntime {
|
class ScriptRuntime {
|
||||||
@@ -34,9 +41,29 @@ namespace Engine {
|
|||||||
explicit ScriptRuntime(ECS::Scene& scene, Application* app = nullptr)
|
explicit ScriptRuntime(ECS::Scene& scene, Application* app = nullptr)
|
||||||
: m_Scene(scene), m_App(app) {}
|
: m_Scene(scene), m_App(app) {}
|
||||||
|
|
||||||
// 每帧调用(吃 scaled dt):对账 → OnCreate(新实例)→ OnUpdate
|
// 每帧调用(吃 scaled dt):对账 → OnCreate(新实例)→
|
||||||
|
// 启停翻转(OnEnable/OnDisable)→ OnStart(首次)→ OnUpdate。
|
||||||
|
// 仅 Running 态被调(Application 门控)
|
||||||
void Update(f32 dt);
|
void Update(f32 dt);
|
||||||
|
|
||||||
|
// 终局(GameFlow →Stopped 广播):全部实例 OnDisable →
|
||||||
|
// OnDestroy 并清空簿记。Unity 退出 Play 语义——成员状态
|
||||||
|
// (动量/边沿/当前 clip)不跨局泄漏,下次 Play 对账重建
|
||||||
|
void EndPlay();
|
||||||
|
|
||||||
|
// 参数热刷新(编辑期一次性):实体全部实例重新 DeclareParams
|
||||||
|
// + 按组件存档灌值(Inspector 编辑参数后调)
|
||||||
|
void RefreshParams(u32 entityBits);
|
||||||
|
|
||||||
|
// 跨脚本按名参数访问(本实体兄弟项或跨实体):SetParam 写
|
||||||
|
// 目标实例成员 + 写穿 Item.params(PIE Inspector 实时反映);
|
||||||
|
// GetParam 读目标实例成员。实体无组件/项不存在/实例未建
|
||||||
|
// 返回 false
|
||||||
|
bool SetParam(u32 entityBits, const std::string& scriptName,
|
||||||
|
const std::string& param, const ParamValue& value);
|
||||||
|
bool GetParam(u32 entityBits, const std::string& scriptName,
|
||||||
|
const std::string& param, ParamValue& out) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Sync();
|
void Sync();
|
||||||
|
|
||||||
|
|||||||
@@ -131,10 +131,11 @@ namespace Engine::Animation {
|
|||||||
std::vector<glm::mat4>& outGlobals)
|
std::vector<glm::mat4>& outGlobals)
|
||||||
{
|
{
|
||||||
// crossfade 双层 = N 层混合的特例(Direct 模式与 Controller
|
// crossfade 双层 = N 层混合的特例(Direct 模式与 Controller
|
||||||
// 过渡共用同一套加权数学)。src 层权重 = 1 - wCur 与历史
|
// 过渡共用同一套加权数学)。两权重和恒为 1,渐进归一化后精确
|
||||||
// mix(src, cur, wCur) 语义等价
|
// 等价 mix(src, cur, wCur):起点全 src、终点全 cur,全程平滑
|
||||||
|
//(cur 恒 1.0 的话起点就瞬间跳 50%,fade 有效时长减半——割裂感)
|
||||||
PoseLayer layers[2];
|
PoseLayer layers[2];
|
||||||
layers[0] = {&cur, curTime, 1.0f};
|
layers[0] = {&cur, curTime, xf.wCur};
|
||||||
u32 count = 1;
|
u32 count = 1;
|
||||||
if (xf.src != nullptr) {
|
if (xf.src != nullptr) {
|
||||||
layers[1] = {xf.src, xf.srcTime, 1.0f - xf.wCur};
|
layers[1] = {xf.src, xf.srcTime, 1.0f - xf.wCur};
|
||||||
|
|||||||
@@ -20,8 +20,40 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <unordered_map>
|
#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,
|
void AnimationSystem::Update(ECS::Scene& scene, Assets::AssetManager& assets,
|
||||||
f32 dt, FrameSceneData& frame)
|
f32 dt, FrameSceneData& frame)
|
||||||
{
|
{
|
||||||
@@ -76,10 +108,11 @@ namespace Engine::Animation {
|
|||||||
ECS::AnimationComponent& anim) {
|
ECS::AnimationComponent& anim) {
|
||||||
if (anim.mode != ECS::AnimationComponent::AnimMode::Direct)
|
if (anim.mode != ECS::AnimationComponent::AnimMode::Direct)
|
||||||
return; // Controller 实体走段 2
|
return; // Controller 实体走段 2
|
||||||
if (!anim.active) return; // 组件禁用 = 暂停采样(播放头不动)
|
|
||||||
if (!mr.model.Valid()) return; // 未指派/手清 = 无效句柄(Get 对无效 FATAL)
|
if (!mr.model.Valid()) return; // 未指派/手清 = 无效句柄(Get 对无效 FATAL)
|
||||||
const Model* model = assets.GetModel(mr.model);
|
const Model* model = assets.GetModel(mr.model);
|
||||||
if (!model || !model->HasAnimations()) return;
|
if (!model || !model->HasAnimations()) return;
|
||||||
|
anim.runtimeModel = model; // 按名播放反指(暂停态也刷新)
|
||||||
|
if (!anim.active) return; // 组件禁用 = 暂停采样(播放头不动)
|
||||||
const auto& clips = model->GetClips();
|
const auto& clips = model->GetClips();
|
||||||
|
|
||||||
// clip 下标合法性(模型资产可能已换)
|
// clip 下标合法性(模型资产可能已换)
|
||||||
@@ -144,10 +177,11 @@ namespace Engine::Animation {
|
|||||||
ECS::AnimationComponent::AnimMode::Controller) {
|
ECS::AnimationComponent::AnimMode::Controller) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!anim.active) return;
|
|
||||||
if (!mr.model.Valid()) return;
|
if (!mr.model.Valid()) return;
|
||||||
const Model* model = assets.GetModel(mr.model);
|
const Model* model = assets.GetModel(mr.model);
|
||||||
if (!model || !model->HasAnimations()) return;
|
if (!model || !model->HasAnimations()) return;
|
||||||
|
anim.runtimeModel = model; // 按名播放反指(暂停态也刷新)
|
||||||
|
if (!anim.active) return;
|
||||||
const auto* ctrl = assets.FindController(anim.controllerPath);
|
const auto* ctrl = assets.FindController(anim.controllerPath);
|
||||||
if (!ctrl) return; // 未加载/路径无效 → rest 兜底
|
if (!ctrl) return; // 未加载/路径无效 → rest 兜底
|
||||||
const auto& states = ctrl->GetStates();
|
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 名 → 下标表
|
// clip 名 → 下标表
|
||||||
auto& lookup = clipLookup[model];
|
auto& lookup = clipLookup[model];
|
||||||
if (lookup.empty()) {
|
if (lookup.empty()) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <Engine/Scripting/ScriptRuntime.h>
|
#include <Engine/Scripting/ScriptRuntime.h>
|
||||||
|
|
||||||
|
#include <Engine/Core/Application.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
#include <Engine/ECS/Components.h>
|
#include <Engine/ECS/Components.h>
|
||||||
|
|
||||||
@@ -18,6 +19,28 @@ namespace Engine {
|
|||||||
return *m_App;
|
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) {
|
void ScriptRuntime::Update(f32 dt) {
|
||||||
Sync();
|
Sync();
|
||||||
|
|
||||||
@@ -26,40 +49,73 @@ namespace Engine {
|
|||||||
const auto e = static_cast<entt::entity>(bits);
|
const auto e = static_cast<entt::entity>(bits);
|
||||||
const ECS::ScriptComponent* sc = reg.valid(e)
|
const ECS::ScriptComponent* sc = reg.valid(e)
|
||||||
? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
|
? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
|
||||||
if (!sc) {
|
|
||||||
continue; // 延迟销毁队列里,下帧 Sync 清理
|
|
||||||
}
|
|
||||||
if (!sc->active) {
|
|
||||||
continue; // 组件总开关:全部实例停 tick(Unity disable)
|
|
||||||
}
|
|
||||||
for (auto& inst : list) {
|
for (auto& inst : list) {
|
||||||
if (!inst.behavior) {
|
if (!inst.behavior) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// per-script 启停:按名查项(对账保证存在;新项要
|
// 启停翻转检测:wantOn = 组件总开关 && 项开关(组件
|
||||||
// 下帧 Sync 才有实例,这里查不到就跳过)
|
// 关闭时零查找;项按名查找沿用每帧既有的那一次)。
|
||||||
const auto item = std::find_if(
|
// 与上帧 inst.enabled 比较出沿:true→OnEnable,
|
||||||
sc->items.begin(), sc->items.end(),
|
// false→OnDisable(Unity enabled 翻转语义)
|
||||||
[&](const ECS::ScriptComponent::Item& it) {
|
bool wantOn = false;
|
||||||
return it.name == inst.behavior->Name();
|
if (sc && sc->active) {
|
||||||
});
|
const auto item = std::find_if(
|
||||||
if (item == sc->items.end() || !item->active) {
|
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
|
continue; // 禁用 = 实例保留停 OnUpdate
|
||||||
}
|
}
|
||||||
if (!inst.created) {
|
if (!inst.created) {
|
||||||
inst.behavior->OnCreate();
|
inst.behavior->OnCreate();
|
||||||
inst.created = true;
|
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);
|
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() {
|
void ScriptRuntime::Sync() {
|
||||||
auto& reg = m_Scene.GetRegistry();
|
auto& reg = m_Scene.GetRegistry();
|
||||||
|
|
||||||
// 1. 孤儿/改名清理:实体或组件没了 → 整组 OnDestroy;
|
// 1. 孤儿/改名清理:实体或组件没了 → 整组 OnDestroy;
|
||||||
// 实例名不在 items 里(改名/移除)→ 单个 OnDestroy
|
// 实例名不在 items 里(改名/移除)→ 单个 OnDestroy。
|
||||||
|
// 销毁次序 = OnDisable(守卫防双发)→ OnDestroy
|
||||||
for (auto it = m_Instances.begin(); it != m_Instances.end();) {
|
for (auto it = m_Instances.begin(); it != m_Instances.end();) {
|
||||||
const auto e = static_cast<entt::entity>(it->first);
|
const auto e = static_cast<entt::entity>(it->first);
|
||||||
const ECS::ScriptComponent* sc =
|
const ECS::ScriptComponent* sc =
|
||||||
@@ -67,9 +123,14 @@ namespace Engine {
|
|||||||
: nullptr;
|
: nullptr;
|
||||||
if (!sc) {
|
if (!sc) {
|
||||||
for (auto& inst : it->second) {
|
for (auto& inst : it->second) {
|
||||||
if (inst.behavior) {
|
if (!inst.behavior) {
|
||||||
inst.behavior->OnDestroy();
|
continue;
|
||||||
}
|
}
|
||||||
|
if (inst.enabled) {
|
||||||
|
inst.enabled = false;
|
||||||
|
inst.behavior->OnDisable();
|
||||||
|
}
|
||||||
|
inst.behavior->OnDestroy();
|
||||||
}
|
}
|
||||||
it = m_Instances.erase(it);
|
it = m_Instances.erase(it);
|
||||||
continue;
|
continue;
|
||||||
@@ -86,6 +147,10 @@ namespace Engine {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (li->behavior) {
|
if (li->behavior) {
|
||||||
|
if (li->enabled) {
|
||||||
|
li->enabled = false;
|
||||||
|
li->behavior->OnDisable();
|
||||||
|
}
|
||||||
li->behavior->OnDestroy();
|
li->behavior->OnDestroy();
|
||||||
}
|
}
|
||||||
li = list.erase(li);
|
li = list.erase(li);
|
||||||
@@ -98,7 +163,9 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 缺失实例化:逐项按名查注册表(未知名警告——场景含未注册
|
// 2. 缺失实例化:逐项按名查注册表(未知名警告——场景含未注册
|
||||||
// 脚本时弱前向兼容,跳过不影响其余脚本)
|
// 脚本时弱前向兼容,跳过不影响其余脚本)。灌值次序对齐
|
||||||
|
// Unity:声明(成员=声明默认)→ 组件存档覆盖 → 收值回调
|
||||||
|
// → OnCreate(= Awake,拿到最终参数值)
|
||||||
const auto view = reg.view<ECS::ScriptComponent>();
|
const auto view = reg.view<ECS::ScriptComponent>();
|
||||||
for (auto [e, sc] : view.each()) {
|
for (auto [e, sc] : view.each()) {
|
||||||
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
const u32 bits = static_cast<u32>(entt::to_integral(e));
|
||||||
@@ -124,6 +191,12 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
behavior->Bind(m_Scene, e, m_App);
|
behavior->Bind(m_Scene, e, m_App);
|
||||||
|
|
||||||
|
ParamBuilder pb;
|
||||||
|
behavior->DeclareParams(pb);
|
||||||
|
pb.Apply(item.params);
|
||||||
|
behavior->SetParamBinds(pb.TakeBinds());
|
||||||
|
behavior->OnParamsApplied();
|
||||||
|
|
||||||
ScriptInstance inst;
|
ScriptInstance inst;
|
||||||
inst.behavior = std::move(behavior);
|
inst.behavior = std::move(behavior);
|
||||||
inst.behavior->OnCreate(); // Unity Awake 语义:实例化即回调
|
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 Scripting
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <Engine/Assets/AssetManager.h>
|
#include <Engine/Assets/AssetManager.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
#include <Engine/Core/UUID.h>
|
#include <Engine/Core/UUID.h>
|
||||||
|
#include <Engine/Scripting/Script.h>
|
||||||
|
|
||||||
#include <yaml-cpp/yaml.h>
|
#include <yaml-cpp/yaml.h>
|
||||||
|
|
||||||
@@ -50,6 +51,11 @@ namespace Engine::Serialization {
|
|||||||
<< YAML::EndSeq;
|
<< 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)
|
void EmitVec4(YAML::Emitter& e, const glm::vec4& v)
|
||||||
{
|
{
|
||||||
e << YAML::Flow << YAML::BeginSeq << v.x << v.y << v.z << v.w
|
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)};
|
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)
|
glm::vec4 ReadVec4(const YAML::Node& n, const glm::vec4& def)
|
||||||
{
|
{
|
||||||
if (!n || !n.IsSequence() || n.size() < 4) return def;
|
if (!n || !n.IsSequence() || n.size() < 4) return def;
|
||||||
@@ -452,6 +523,8 @@ namespace Engine::Serialization {
|
|||||||
<< cc->maxSlopeAngle;
|
<< cc->maxSlopeAngle;
|
||||||
out << YAML::Key << "StepHeight" << YAML::Value
|
out << YAML::Key << "StepHeight" << YAML::Value
|
||||||
<< cc->stepHeight;
|
<< cc->stepHeight;
|
||||||
|
out << YAML::Key << "GravityScale" << YAML::Value
|
||||||
|
<< cc->gravityScale;
|
||||||
out << YAML::Key << "Active" << YAML::Value << cc->active;
|
out << YAML::Key << "Active" << YAML::Value << cc->active;
|
||||||
out << YAML::EndMap;
|
out << YAML::EndMap;
|
||||||
}
|
}
|
||||||
@@ -467,6 +540,39 @@ namespace Engine::Serialization {
|
|||||||
out << YAML::BeginMap;
|
out << YAML::BeginMap;
|
||||||
out << YAML::Key << "Name" << YAML::Value << item.name;
|
out << YAML::Key << "Name" << YAML::Value << item.name;
|
||||||
out << YAML::Key << "Active" << YAML::Value << item.active;
|
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::EndMap;
|
||||||
}
|
}
|
||||||
out << YAML::EndSeq;
|
out << YAML::EndSeq;
|
||||||
@@ -855,6 +961,7 @@ namespace Engine::Serialization {
|
|||||||
cc.mass = n["Mass"].as<f32>(cc.mass);
|
cc.mass = n["Mass"].as<f32>(cc.mass);
|
||||||
cc.maxSlopeAngle = n["MaxSlopeAngle"].as<f32>(cc.maxSlopeAngle);
|
cc.maxSlopeAngle = n["MaxSlopeAngle"].as<f32>(cc.maxSlopeAngle);
|
||||||
cc.stepHeight = n["StepHeight"].as<f32>(cc.stepHeight);
|
cc.stepHeight = n["StepHeight"].as<f32>(cc.stepHeight);
|
||||||
|
cc.gravityScale = n["GravityScale"].as<f32>(cc.gravityScale);
|
||||||
cc.active = n["Active"].as<bool>(true);
|
cc.active = n["Active"].as<bool>(true);
|
||||||
// 状态(速度/着地)由 PhysicsWorld 对账重建,不存
|
// 状态(速度/着地)由 PhysicsWorld 对账重建,不存
|
||||||
}
|
}
|
||||||
@@ -868,6 +975,27 @@ namespace Engine::Serialization {
|
|||||||
ECS::ScriptComponent::Item item;
|
ECS::ScriptComponent::Item item;
|
||||||
item.name = it["Name"].as<std::string>("");
|
item.name = it["Name"].as<std::string>("");
|
||||||
item.active = it["Active"].as<bool>(true);
|
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 已杜绝重复,
|
// 去重兜底:同名项只收第一份(UI 已杜绝重复,
|
||||||
// 手编场景仍可能写出重复)
|
// 手编场景仍可能写出重复)
|
||||||
const bool dup = std::any_of(
|
const bool dup = std::any_of(
|
||||||
|
|||||||
Reference in New Issue
Block a user