Add animation controller system, material assets and skeleton visualization
- .animctrl state machine asset (params/transitions/blend trees, 1D+2D directional blending) with Unity Animator-style editor panel: state list, transitions, params, directional pad preview driving live CrossFade on the selected entity - Split animation evaluation out of SceneRenderer into AnimationSystem/AnimationEvaluator; skin palettes with rest-pose fallback and blend-weight epsilon filtering - MaterialAsset (.material) with reload-safe preview tile pool (DropMaterial clears dirty refs before instance destruction) - EditorGizmoPass skeleton wires + viewport joint overlay; camera projection rework (baked Vulkan Y-flip, ortho support) - Editor/game build split via HELLOVULKAN_EDITOR option
This commit is contained in:
@@ -169,6 +169,25 @@ namespace Engine::Serialization {
|
||||
return ref;
|
||||
}
|
||||
|
||||
// 材质覆盖路径解析:guid 反查优先(资产移动不断引用),path
|
||||
// 兜底;解析成功即预载——"收集期只查缓存"不变量在此保证
|
||||
//(游戏形态无编辑器,场景读后预载是唯一自动加载点),未加载
|
||||
// 成功也返回路径(收集期回退内建材质,不丢实体)
|
||||
std::string ResolveMaterialPath(Assets::AssetManager& assets,
|
||||
u64 guid, const std::string& path)
|
||||
{
|
||||
std::string resolved;
|
||||
if (guid != 0) resolved = assets.ResolveGUID(guid);
|
||||
if (resolved.empty()) resolved = path;
|
||||
if (resolved.empty()) return {};
|
||||
|
||||
if (!assets.IsMaterialLoaded(resolved)) {
|
||||
assets.LoadMaterialAsset(resolved);
|
||||
}
|
||||
assets.GetOrCreateGUID(resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
// ---- 中间结构:解析全部成功才碰 registry ----
|
||||
// comps 持 Components 节点(仅在键存在且为 map 时赋值——
|
||||
// yaml-cpp 新版对僵尸节点做 operator= 会抛异常)
|
||||
@@ -229,6 +248,25 @@ namespace Engine::Serialization {
|
||||
out << YAML::Key << "Path" << YAML::Value << path;
|
||||
out << YAML::EndMap; // Model(flow)
|
||||
out << YAML::Key << "Active" << YAML::Value << mr->active;
|
||||
// 材质覆盖表(UE Material Slot):seq of {Slot,Guid,Path}
|
||||
if (!mr->materialOverrides.empty()) {
|
||||
out << YAML::Key << "MaterialOverrides"
|
||||
<< YAML::Value << YAML::BeginSeq;
|
||||
for (const auto& [slot, mpath] :
|
||||
mr->materialOverrides) {
|
||||
out << YAML::BeginMap;
|
||||
out << YAML::Key << "Slot" << YAML::Value << slot;
|
||||
if (const u64 guid = assets.GetOrCreateGUID(mpath);
|
||||
guid != 0) {
|
||||
out << YAML::Key << "Guid"
|
||||
<< YAML::Value << guid;
|
||||
}
|
||||
out << YAML::Key << "Path"
|
||||
<< YAML::Value << mpath;
|
||||
out << YAML::EndMap;
|
||||
}
|
||||
out << YAML::EndSeq;
|
||||
}
|
||||
out << YAML::EndMap; // MeshRenderer
|
||||
}
|
||||
else {
|
||||
@@ -244,7 +282,31 @@ namespace Engine::Serialization {
|
||||
out << YAML::Key << "Playing" << YAML::Value << a->playing;
|
||||
out << YAML::Key << "Loop" << YAML::Value << a->loop;
|
||||
out << YAML::Key << "Speed" << YAML::Value << a->speed;
|
||||
out << YAML::Key << "FadeDuration" << YAML::Value
|
||||
<< a->fadeDuration;
|
||||
out << YAML::Key << "Active" << YAML::Value << a->active;
|
||||
// Controller 模式(Unity Animator 语义):mode/路径/参数
|
||||
// 实例值落盘;状态机播放头瞬态不落盘(重启从 entry 开始)
|
||||
out << YAML::Key << "Mode" << YAML::Value
|
||||
<< static_cast<int>(a->mode);
|
||||
out << YAML::Key << "ControllerPath" << YAML::Value
|
||||
<< a->controllerPath;
|
||||
if (!a->floatParams.empty()) {
|
||||
out << YAML::Key << "FloatParams" << YAML::Value
|
||||
<< YAML::BeginMap;
|
||||
for (const auto& [k, v] : a->floatParams) {
|
||||
out << YAML::Key << k << YAML::Value << v;
|
||||
}
|
||||
out << YAML::EndMap;
|
||||
}
|
||||
if (!a->boolParams.empty()) {
|
||||
out << YAML::Key << "BoolParams" << YAML::Value
|
||||
<< YAML::BeginMap;
|
||||
for (const auto& [k, v] : a->boolParams) {
|
||||
out << YAML::Key << k << YAML::Value << v;
|
||||
}
|
||||
out << YAML::EndMap;
|
||||
}
|
||||
// 外部动画源(编辑器 attach 列表):clip 本体运行时并入
|
||||
// Model 资产不落盘,加载端按此列表幂等重放
|
||||
out << YAML::Key << "Attached" << YAML::Value << YAML::Flow
|
||||
@@ -261,6 +323,17 @@ namespace Engine::Serialization {
|
||||
<< YAML::BeginMap;
|
||||
out << YAML::Key << "Primary" << YAML::Value << c->primary;
|
||||
out << YAML::Key << "Active" << YAML::Value << c->active;
|
||||
// 投影参数(Camera 成员):老场景缺键时读侧回退默认值
|
||||
out << YAML::Key << "ProjectionMode" << YAML::Value
|
||||
<< static_cast<int>(c->camera.GetProjectionMode());
|
||||
out << YAML::Key << "FovDeg" << YAML::Value
|
||||
<< c->camera.GetFovDeg();
|
||||
out << YAML::Key << "NearZ" << YAML::Value
|
||||
<< c->camera.GetNearZ();
|
||||
out << YAML::Key << "FarZ" << YAML::Value
|
||||
<< c->camera.GetFarZ();
|
||||
out << YAML::Key << "OrthoSize" << YAML::Value
|
||||
<< c->camera.GetOrthoSize();
|
||||
out << YAML::EndMap;
|
||||
}
|
||||
|
||||
@@ -598,8 +671,25 @@ namespace Engine::Serialization {
|
||||
ref.Valid()) {
|
||||
handle.AddComponent<ECS::MeshRendererComponent>(
|
||||
ECS::MeshRendererComponent{ref});
|
||||
handle.GetComponent<ECS::MeshRendererComponent>().active =
|
||||
auto& mrComp =
|
||||
handle.GetComponent<ECS::MeshRendererComponent>();
|
||||
mrComp.active =
|
||||
n["Active"].as<bool>(true); // 缺省 true 向前兼容
|
||||
// 材质覆盖表:逐项解析(guid 反查 → path 兜底)
|
||||
// 并预载;加载失败保留路径(收集期回退内建材质)
|
||||
if (const YAML::Node ovs = n["MaterialOverrides"]) {
|
||||
for (const auto& ov : ovs) {
|
||||
const u32 slot = ov["Slot"].as<u32>(
|
||||
0xFFFFFFFFu);
|
||||
const auto opath = ResolveMaterialPath(
|
||||
assets, ov["Guid"].as<u64>(0),
|
||||
ov["Path"].as<std::string>(""));
|
||||
if (slot == 0xFFFFFFFFu || opath.empty()) {
|
||||
continue;
|
||||
}
|
||||
mrComp.materialOverrides[slot] = opath;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
ENGINE_WARN("SceneSerializer: model unresolved "
|
||||
@@ -616,7 +706,33 @@ namespace Engine::Serialization {
|
||||
a.playing = n["Playing"].as<bool>(a.playing);
|
||||
a.loop = n["Loop"].as<bool>(a.loop);
|
||||
a.speed = n["Speed"].as<f32>(a.speed);
|
||||
a.fadeDuration =
|
||||
n["FadeDuration"].as<f32>(a.fadeDuration); // 缺键回退 0.3
|
||||
a.active = n["Active"].as<bool>(true);
|
||||
// Controller 模式缺键回退 Direct(老场景零迁移);
|
||||
// mode 越界值也视为 Direct
|
||||
if (const int m = n["Mode"].as<int>(0); m == 1) {
|
||||
a.mode = ECS::AnimationComponent::AnimMode::Controller;
|
||||
}
|
||||
a.controllerPath = n["ControllerPath"].as<std::string>("");
|
||||
// Controller 预载(收集期只查缓存的不变量在此保证;
|
||||
// 解析失败保留路径,System Find 落空 → rest 兜底)
|
||||
if (!a.controllerPath.empty() &&
|
||||
!assets.IsControllerLoaded(a.controllerPath)) {
|
||||
assets.LoadController(a.controllerPath);
|
||||
}
|
||||
if (const YAML::Node fp = n["FloatParams"]) {
|
||||
for (const auto& it : fp) {
|
||||
a.floatParams[it.first.as<std::string>("")] =
|
||||
it.second.as<float>(0.0f);
|
||||
}
|
||||
}
|
||||
if (const YAML::Node bp = n["BoolParams"]) {
|
||||
for (const auto& it : bp) {
|
||||
a.boolParams[it.first.as<std::string>("")] =
|
||||
it.second.as<bool>(false);
|
||||
}
|
||||
}
|
||||
if (const YAML::Node att = n["Attached"]) {
|
||||
for (const auto& it : att) {
|
||||
a.attachedAnimPaths.push_back(
|
||||
@@ -629,6 +745,18 @@ namespace Engine::Serialization {
|
||||
auto& c = handle.AddComponent<ECS::CameraComponent>();
|
||||
c.primary = n["Primary"].as<bool>(false);
|
||||
c.active = n["Active"].as<bool>(true);
|
||||
// 投影参数缺省回退默认值(Version 向后兼容:老场景无新键
|
||||
// 可载)。ProjectionMode 越界值视为透视
|
||||
if (const int pm = n["ProjectionMode"].as<int>(0);
|
||||
pm == 1) {
|
||||
c.camera.SetProjectionMode(
|
||||
Engine::Camera::ProjectionMode::Orthographic);
|
||||
}
|
||||
c.camera.SetFovDeg(n["FovDeg"].as<f32>(c.camera.GetFovDeg()));
|
||||
c.camera.SetClip(n["NearZ"].as<f32>(c.camera.GetNearZ()),
|
||||
n["FarZ"].as<f32>(c.camera.GetFarZ()));
|
||||
c.camera.SetOrthoSize(
|
||||
n["OrthoSize"].as<f32>(c.camera.GetOrthoSize()));
|
||||
}
|
||||
|
||||
if (const YAML::Node n = comps["Light"]) {
|
||||
|
||||
Reference in New Issue
Block a user