This commit is contained in:
Rowland 2026-07-13 15:28:35 +08:00
parent 08d528c686
commit 278313f739
15 changed files with 2719 additions and 188 deletions

View File

@ -10,6 +10,57 @@ body {
border-radius: 4px;
}
.mcui-Panel {
overflow: hidden;
}
.mcui-Text {
background-color: transparent;
border-color: transparent;
}
.mcui-Button {
text-align: center;
cursor: pointer;
border-width: 1px;
border-bottom-width: 3px;
border-color: rgba(150, 190, 255, 200);
}
.mcui-Button.mcui-disabled {
cursor: default;
}
.mcui-Image {
background-color: transparent;
border-radius: 0;
}
.mcui-node-Button {
background-color: rgba(46, 87, 148, 0.940);
border-top-color: rgba(117, 144, 184, 1.000);
border-left-color: rgba(79, 114, 165, 1.000);
border-right-color: rgba(27, 50, 86, 1.000);
border-bottom-color: rgba(27, 50, 86, 1.000);
}
.mcui-node-Button:hover {
background-color: rgba(79, 114, 165, 0.970);
border-top-color: rgba(132, 156, 192, 1.000);
}
.mcui-node-Button:active {
background-color: rgba(36, 68, 115, 0.980);
border-top-width: 3px;
border-bottom-width: 1px;
border-top-color: rgba(27, 50, 86, 1.000);
border-left-color: rgba(27, 50, 86, 1.000);
border-right-color: rgba(117, 144, 184, 1.000);
border-bottom-color: rgba(117, 144, 184, 1.000);
}
.mcui-node-Button.mcui-disabled,
.mcui-node-Button.mcui-disabled:hover,
.mcui-node-Button.mcui-disabled:active {
background-color: rgba(46, 48, 54, 0.720);
color: rgba(140, 148, 163, 1.000);
border-width: 1px;
border-color: rgba(77, 82, 92, 1.000);
}

View File

@ -2,9 +2,8 @@
<head>
<link type="text/rcss" href="main_menu.rcss" />
</head>
<body style="position:relative; width:1920px; height:1080px;">
<button id="Button" class="mcui mcui-Button" data-metacore-action="Button" style="position:absolute; left:1460px; top:205px; width:295px; height:199px; color:rgba(255, 255, 255, 1.000); background-color:rgba(46, 87, 148, 0.850); font-size:16px; padding:8px 8px;">按钮
<div id="Text" class="mcui mcui-Text" style="position:absolute; left:522px; top:62px; width:330px; height:249px; color:rgba(255, 255, 255, 1.000); background-color:rgba(31, 36, 46, 0.000); font-size:16px; padding:8px 8px;">文本</div>
</button>
<body style="position:relative; width:1980px; height:1080px; background-color:rgba(0, 0, 0, 0.000);">
<button id="Button" class="mcui mcui-Button mcui-node-Button" data-metacore-action="Button" style="position:absolute; left:368px; top:400px; width:256px; height:128px; color:rgba(255, 255, 255, 1.000); font-size:16px; padding:8px 8px; text-align:center; display:flex; align-items:center; justify-content:center;">按钮</button>
<div id="Text" class="mcui mcui-Text mcui-node-Text" style="position:absolute; left:992px; top:40px; width:376px; height:264px; color:rgba(240, 245, 255, 1.000); font-size:16px; padding:8px 8px; text-align:left; background-color:rgba(0, 0, 0, 0.000); display:flex; align-items:flex-start; justify-content:flex-start;">文本</div>
</body>
</rml>

View File

@ -3196,7 +3196,7 @@ void MetaCoreDrawUiRendererComponentInspector(MetaCoreEditorContext& editorConte
editorContext.GetScene().IncrementRevision();
}
ImGui::TextDisabled("2D 使用屏幕空间层级3D 使用对象变换、世界尺寸和每单位像素。");
ImGui::TextDisabled("2D 使用屏幕空间层级3D 使用对象位置/旋转、世界尺寸和每单位像素。");
}
void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -763,6 +763,14 @@ void MetaCoreEditorContext::ClearSelectedAssetSubId() {
void MetaCoreEditorContext::SetDockLayoutBuilt(bool built) { DockLayoutBuilt_ = built; }
bool MetaCoreEditorContext::HasDockLayoutBuilt() const { return DockLayoutBuilt_; }
MetaCoreEditorWorkspacePage MetaCoreEditorContext::GetWorkspacePage() const {
return WorkspacePage_;
}
void MetaCoreEditorContext::SetWorkspacePage(MetaCoreEditorWorkspacePage page) {
WorkspacePage_ = page;
}
void MetaCoreEditorContext::NormalizeSelection() {
std::vector<MetaCoreId> sanitizedSelection;
sanitizedSelection.reserve(SelectedObjectIds_.size());

View File

@ -43,6 +43,12 @@ enum class MetaCoreReparentTransformRule {
KeepLocal
};
enum class MetaCoreEditorWorkspacePage {
Scene = 0,
Game,
UiDesigner
};
MC_STRUCT()
struct MetaCoreEditorStateSnapshot {
MC_GENERATED_BODY()
@ -187,6 +193,8 @@ public:
void ClearSelectedAssetSubId();
void SetDockLayoutBuilt(bool built);
[[nodiscard]] bool HasDockLayoutBuilt() const;
[[nodiscard]] MetaCoreEditorWorkspacePage GetWorkspacePage() const;
void SetWorkspacePage(MetaCoreEditorWorkspacePage page);
private:
void NormalizeSelection();
@ -221,6 +229,7 @@ private:
MetaCoreSelectedAssetState SelectedAsset_{};
std::filesystem::path SelectedProjectDirectory_{"Assets"};
std::string SelectedAssetSubId_{};
MetaCoreEditorWorkspacePage WorkspacePage_ = MetaCoreEditorWorkspacePage::Scene;
};
} // namespace MetaCore

View File

@ -298,6 +298,17 @@ public:
[[nodiscard]] virtual bool RevertSelectedPrefabInstance(MetaCoreEditorContext& editorContext) = 0;
};
class MetaCoreIUiDesignerService : public MetaCoreIEditorService {
public:
[[nodiscard]] virtual bool OpenUiDocument(
MetaCoreEditorContext& editorContext,
const MetaCoreAssetRecord& assetRecord
) = 0;
virtual void DrawDesignerPage(MetaCoreEditorContext& editorContext) = 0;
[[nodiscard]] virtual bool HasDirtyDocument() const = 0;
};
class MetaCoreIComponentTypeRegistry : public MetaCoreIEditorService {
public:
struct MetaCoreComponentDescriptor {

View File

@ -197,43 +197,6 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met
});
}
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>() &&
gameObject.HasComponent<MetaCoreUiRendererComponent>()) {
const auto& uiRenderer = gameObject.GetComponent<MetaCoreUiRendererComponent>();
if (uiRenderer.Visible && uiRenderer.RenderMode == MetaCoreUiRenderMode::WorldSpace) {
snapshot.Renderables.push_back(MetaCoreRenderSyncRenderable{
objectId,
parentId,
gameObject.GetName(),
localMatrix,
worldMatrix,
true,
MetaCoreMeshSourceKind::Builtin,
MetaCoreBuiltinMeshType::Cube,
MetaCoreAssetGuid{},
MetaCoreAssetGuid{},
{},
-1,
objectId,
gameObject.GetName(),
false,
{},
glm::vec3(0.12F, 0.38F, 0.78F),
0.0F,
0.38F,
glm::vec3(0.02F, 0.08F, 0.16F),
MetaCoreMeshAlphaMode::Opaque,
0.5F,
true,
{},
{},
{},
{},
{}
});
}
}
if (gameObject.HasComponent<MetaCoreCameraComponent>()) {
const auto& camera = gameObject.GetComponent<MetaCoreCameraComponent>();
MetaCoreRenderSyncCamera syncCamera;

View File

@ -715,6 +715,9 @@ static json UiNodeToJson(const MetaCoreUiNodeDocument& node) {
j["Style"] = UiStyleToJson(node.Style);
j["Text"] = node.Text;
j["Interactable"] = node.Interactable;
j["Action"] = node.Action;
j["DataBinding"] = node.DataBinding;
j["DataFormat"] = node.DataFormat;
return j;
}
@ -730,6 +733,9 @@ static MetaCoreUiNodeDocument JsonToUiNode(const json& j) {
if (j.contains("Style")) node.Style = JsonToUiStyle(j["Style"]);
if (j.contains("Text")) node.Text = j["Text"].get<std::string>();
if (j.contains("Interactable")) node.Interactable = j["Interactable"].get<bool>();
if (j.contains("Action")) node.Action = j["Action"].get<std::string>();
if (j.contains("DataBinding")) node.DataBinding = j["DataBinding"].get<std::string>();
if (j.contains("DataFormat")) node.DataFormat = j["DataFormat"].get<std::string>();
return node;
}
@ -744,6 +750,8 @@ bool MetaCoreSceneSerializer::SaveUiToJson(
uiJson["Name"] = uiDocument.Name;
uiJson["ReferenceWidth"] = uiDocument.ReferenceWidth;
uiJson["ReferenceHeight"] = uiDocument.ReferenceHeight;
uiJson["BackgroundColor"] = Vec3ToJson(uiDocument.BackgroundColor);
uiJson["BackgroundAlpha"] = uiDocument.BackgroundAlpha;
uiJson["RootNodeIds"] = uiDocument.RootNodeIds;
json nodesArray = json::array();
@ -780,6 +788,8 @@ std::optional<MetaCoreUiDocument> MetaCoreSceneSerializer::LoadUiFromJson(
if (uiJson.contains("Name")) doc.Name = uiJson["Name"].get<std::string>();
if (uiJson.contains("ReferenceWidth")) doc.ReferenceWidth = uiJson["ReferenceWidth"].get<std::int32_t>();
if (uiJson.contains("ReferenceHeight")) doc.ReferenceHeight = uiJson["ReferenceHeight"].get<std::int32_t>();
if (uiJson.contains("BackgroundColor")) doc.BackgroundColor = JsonToVec3(uiJson["BackgroundColor"]);
if (uiJson.contains("BackgroundAlpha")) doc.BackgroundAlpha = uiJson["BackgroundAlpha"].get<float>();
if (uiJson.contains("RootNodeIds")) doc.RootNodeIds = uiJson["RootNodeIds"].get<std::vector<std::string>>();
if (uiJson.contains("Nodes") && uiJson["Nodes"].is_array()) {

View File

@ -196,6 +196,15 @@ struct MetaCoreUiNodeDocument {
MC_PROPERTY()
bool Interactable = false;
MC_PROPERTY()
std::string Action{};
MC_PROPERTY()
std::string DataBinding{};
MC_PROPERTY()
std::string DataFormat{};
};
MC_STRUCT()
@ -211,6 +220,12 @@ struct MetaCoreUiDocument {
MC_PROPERTY()
std::int32_t ReferenceHeight = 1080;
MC_PROPERTY()
glm::vec3 BackgroundColor{0.0F, 0.0F, 0.0F};
MC_PROPERTY()
float BackgroundAlpha = 0.0F;
MC_PROPERTY()
std::vector<std::string> RootNodeIds{};

View File

@ -28,6 +28,7 @@
2. 引擎能力与里程碑
- [metacore-engine-feature-baseline.md](D:/MetaCore/docs/designs/metacore-engine-feature-baseline.md)
- [metacore-mature-engine-gap-and-work-plan.md](D:/MetaCore/docs/designs/metacore-mature-engine-gap-and-work-plan.md)
- [metacore-phase1-roadmap.md](D:/MetaCore/docs/designs/metacore-phase1-roadmap.md)
- [metacore-phase1-m1-m4-tasklist-and-acceptance.md](D:/MetaCore/docs/designs/metacore-phase1-m1-m4-tasklist-and-acceptance.md)
- [metacore-phase1-m1-m4-workstreams.md](D:/MetaCore/docs/designs/metacore-phase1-m1-m4-workstreams.md)
@ -41,16 +42,17 @@
## 当前执行建议
如果现在开始第一阶段开发,建议直接从下面份文档开始:
如果现在开始第一阶段开发,建议直接从下面份文档开始:
- [metacore-mature-engine-gap-and-work-plan.md](D:/MetaCore/docs/designs/metacore-mature-engine-gap-and-work-plan.md)
- [metacore-phase1-roadmap.md](D:/MetaCore/docs/designs/metacore-phase1-roadmap.md)
- [metacore-m1-p0-task-cards.md](D:/MetaCore/docs/designs/metacore-m1-p0-task-cards.md)
对应执行顺序:
1. 先按 roadmap 对齐阶段目标
2. 再按 `M1 P0` 开任务卡
3. 先做项目路径、项目文件和 startup scene 主链
1. 先用差距与工作计划确认当前 P0 阻塞项
2. 再按 roadmap 对齐阶段目标
3. 把尚未完成的 P0 项拆入具体任务卡和验收矩阵
## 专项设计文档

View File

@ -0,0 +1,339 @@
# MetaCore 与成熟引擎的差距及工作计划
更新时间2026-07-13
状态:当前差距评估与执行建议
读者:产品、架构、引擎、编辑器、渲染、工具链、测试、交付
## 目的
本文基于当前仓库代码、测试和已有设计文档,回答两个问题:
1. MetaCore 与 Unity、Unreal Engine、Godot 等成熟通用引擎相比,还缺少什么。
2. 在 MetaCore 当前“工业仿真、数字孪生、国产化 C/S 交付”的产品定位下,接下来真正需要做什么。
本文不是要求第一阶段复制成熟引擎的全部功能,而是把“通用能力差距”和“当前产品必须补齐的能力”分开管理,避免功能面无限扩张。
## 当前结论
MetaCore 已经超过单纯编辑器 Demo 阶段,当前具备:
- EnTT ECS 与 `GameObject + Component` 场景语义。
- 场景层级、选择、复制、删除、重挂接、保存和加载。
- ImGui 编辑器壳、Hierarchy、Scene、Inspector、Project、Console 等基础工作流。
- Filament 场景渲染、PBR、glTF/GLB、环境光、基础阴影和编辑器离屏视口。
- GUID、meta、Asset Database、导入、重导入、依赖图、热重载和 Cook 基础链路。
- 基础 Prefab 创建、实例化、Apply 和 Revert。
- 原生 C++ 行为注册及 `OnPlayStart / FixedUpdate / Update / LateUpdate / OnPlayStop` 生命周期。
- glTF 动画元数据导入、Clip 选择、循环、速度控制和运行时播放。
- RmlUi 运行时 UI、UI 文档和可视编辑工作流基础。
- 独立 Player、RuntimeData TCP/文件回放、场景绑定和诊断。
截至 2026-07-13当前 `MetaCoreSmokeTests` 构建成功并全部通过,一次完整运行耗时约 65.90 秒。
综合判断:项目处于“早期 Alpha 的垂直引擎底座”阶段。架构主线已经成立,但生产可用性、运行时系统广度、性能工程和发布可靠性仍明显弱于成熟引擎。
## 差距总览
| 能力域 | 当前基础 | 主要问题 | 产品优先级 |
| --- | --- | --- | --- |
| 发布与部署 | Player、Cook、运行时配置 | 尚未形成可重复、可验证的完整交付包 | P0 |
| 资产管线 | GUID、meta、依赖、热重载、glTF | Import Settings、派生数据、预览和引用修复不完整 | P0 |
| 用户逻辑开发 | 原生 C++ 行为和生命周期 | 缺用户脚本工程、编译热重载、调试和通用事件/时间系统 | P0 |
| 输入与运行时交互 | 键鼠采集、编辑器交互 | 缺 Input Map、运行时事件和统一输入路由 | P0 |
| 物理 | 未形成独立模块 | 缺碰撞、刚体、射线、触发器和角色控制 | P1有仿真交互需求时升 P0 |
| 渲染生产力 | Filament、PBR、glTF、环境光 | 灯光、阴影、后处理、LOD、粒子和调试视图不足 | P1 |
| 动画 | Clip 导入与播放 | 缺状态机、混合、重定向、Root Motion 和动画工具 | P1 |
| UI | RmlUi 运行时与编辑器基础 | 控件、事件、适配、数据绑定和编辑/运行一致性不足 | P1 |
| Scene / Prefab | 单场景和基础 Prefab | 缺多场景、流式加载、嵌套 Prefab、Variant 和细粒度 Override | P1 |
| 工程基础设施 | CMake、代码生成、Smoke Tests | 缺分层测试、性能回归、GPU 回归、CI 和崩溃诊断 | P0/P1 |
| 音频 | 无正式系统 | 缺完整音频资源和运行时 | P2 |
| 导航 / AI | 无正式系统 | 缺 NavMesh、寻路、避障和调试工具 | P2 |
| XR / Web | 主要为架构预留 | 缺 OpenXR 与 Web 构建交付闭环 | 后续阶段 |
## P0当前必须解决的问题
### 1. 发布与部署还不具备正式交付厚度
#### 问题
- Cook 已存在,但尚未形成统一的一键 Build / Package 工作流。
- 缺少 Debug、Development、Release 以及目标平台构建 Profile。
- 缺少运行库、Filament、字体、材质和项目内容的自动依赖收集。
- 缺少从空输出目录构建以及 clean machine 运行验证。
- Linux Player 启动流程仍存在未接入路径。
- 缺少发布清单、引擎/资产版本兼容规则、补丁和升级策略。
- 现场崩溃后的日志、Dump、符号和诊断收集能力不足。
#### 需要做的事
- [ ] 定义统一 `BuildProfile`:平台、配置、图形后端、启动场景、输出目录和资源策略。
- [ ] 建立 `Build -> Cook -> Stage -> Package -> Validate` 命令链。
- [ ] 自动收集可执行文件、动态库、Filament 材质、RmlUi、字体、配置和 cooked 资产。
- [ ] 生成包含文件 Hash、版本和依赖信息的发布 Manifest。
- [ ] 完成 Windows 与 Linux Player 启动和路径规则统一。
- [ ] 建立 clean-machine 或干净容器验证脚本。
- [ ] 增加缺文件、损坏资产、版本不兼容时的明确错误报告。
- [ ] 建立崩溃 Dump、日志归档和现场诊断包导出能力。
#### 验收标准
- 从干净输出目录执行一次命令即可生成完整交付包。
- 交付包不依赖源码目录、vcpkg 安装目录或编辑器工作目录。
- 在未安装开发工具链的目标机器上可以启动、加载启动场景并显示 UI。
- 删除任一必需文件后,验证工具能够在交付前报告具体缺失项。
### 2. 资产管线还不是生产级
#### 问题
- glTF/GLB 是当前正式主线,但 FBX/OBJ 等格式仍偏占位或不完整。
- 纹理缺少色彩空间、压缩、Mipmap、最大尺寸、法线贴图等 Import Settings。
- 缺少稳定的缩略图、资源预览和派生数据缓存。
- 批量导入和复杂重导入可能阻塞编辑器主线程。
- 移动、重命名、删除和重导入后的引用检查与修复工具仍不完整。
- 子资产、平台差异化 Cook 和缓存失效规则仍需强化。
#### 需要做的事
- [ ] 明确 glTF/GLB 的正式支持矩阵和导入错误规范。
- [ ] 删除或明确标识通用占位导入结果,避免占位资产被当成成功导入。
- [ ] 建立 Model、Texture、Material 的版本化 Import Settings。
- [ ] 实现纹理色彩空间、Mipmap、尺寸、压缩和法线贴图设置。
- [ ] 建立缩略图与预览缓存,并定义缓存失效条件。
- [ ] 把大型模型解析、纹理处理和派生数据生成移出 UI 主线程。
- [ ] 增加资源引用检查、孤立资源检查和 GUID/路径修复工具。
- [ ] 建立导入黄金样本库,覆盖层级、材质、贴图、动画和损坏输入。
#### 验收标准
- 同一源资产和相同设置可生成稳定一致的导入结果。
- 重导入不改变稳定子资产 GUID也不破坏 Scene、Prefab 和 Material 引用。
- 大型资产导入期间编辑器仍可响应,并显示进度、取消和错误信息。
- 资产移动、重命名或删除后可准确列出受影响引用。
### 3. 用户逻辑开发闭环不完整
#### 问题
- 当前原生 Script Registry 能承载内置行为,但还不是面向项目开发者的成熟脚本系统。
- 缺少用户脚本项目的创建、编译、加载、卸载和热重载流程。
- 缺少稳定的脚本 API、组件访问、安全失效和版本兼容规则。
- 缺少脚本断点、调用栈、字段错误和生命周期异常诊断。
- 缺少统一事件总线、定时器、协程或任务调度抽象。
#### 需要做的事
- [ ] 明确第一阶段脚本路线:原生 C++ 插件、嵌入式脚本语言,或二者的阶段关系。
- [ ] 建立项目脚本模板、构建目标和编辑器编译入口。
- [ ] 支持脚本模块加载、卸载和失败回退。
- [ ] 定义脚本 API 稳定边界和对象/组件句柄失效规则。
- [ ] 支持可序列化字段、资源引用和 GameObject 引用。
- [ ] 增加统一时间、定时器和事件系统。
- [ ] 增加脚本错误定位、日志上下文和运行时异常隔离。
- [ ] 在明确安全边界后实现脚本热重载。
#### 验收标准
- 用户可以在项目目录创建一个新行为并在编辑器中完成编译和挂载。
- 行为字段可编辑、保存、加载,并进入 Player。
- 编译或加载失败不会破坏当前场景,且能定位到具体脚本和错误。
- Play/Stop 后运行态修改按照明确规则恢复或保留。
### 4. 缺少通用运行时输入与交互层
#### 问题
- 当前输入主要服务编辑器相机、选择和 Gizmo。
- 缺少 Action / Axis、输入配置资源和用户重绑定。
- 3D 对象、运行时 UI 和脚本之间缺少统一事件路由。
- 缺少运行时 Raycast、Hover、Click、Drag 等高层交互语义。
#### 需要做的事
- [ ] 定义 Input Action、Axis、Binding 和 Input Context 数据模型。
- [ ] 支持键盘、鼠标和基础手柄映射。
- [ ] 建立编辑器输入、Game View 输入和 Player 输入的焦点规则。
- [ ] 统一 RmlUi 与 3D 场景输入路由和事件消费顺序。
- [ ] 为运行时对象提供点击、悬停、拖拽和射线命中事件。
- [ ] 将输入配置纳入项目、Cook 和 Player 加载链路。
#### 验收标准
- 同一份输入配置可以在编辑器 Play Mode 和独立 Player 中工作。
- UI 捕获输入时不会错误操作 3D 场景。
- 用户可修改按键绑定而不重新编译项目。
### 5. 工程验证不足以支撑长期交付
#### 问题
- 测试覆盖面已有不错基础,但大部分集中在一个耗时约一分钟的 Smoke Test 程序中。
- 缺少模块级单元测试、GPU 截图回归、性能基准和长稳测试。
- 缺少 Windows、Linux、统信、麒麟等目标平台的持续构建矩阵。
- 缺少内存、资源生命周期、启动时间和场景帧时间回归门槛。
#### 需要做的事
- [ ] 把 Smoke Tests 拆分为 Foundation、Scene、Assets、Editor、RuntimeData、RuntimeUi 等测试目标。
- [ ] 保留端到端 Smoke Tests同时缩短单次反馈时间。
- [ ] 增加 glTF、Scene、Prefab、Material、UI 的黄金文件回归测试。
- [ ] 增加 Filament 关键场景截图对比测试。
- [ ] 建立启动、导入、加载、帧时间和内存基准。
- [ ] 建立目标平台 CI 构建与 Player 启动检查。
- [ ] 增加 AddressSanitizer、UndefinedBehaviorSanitizer 或对应平台内存诊断任务。
#### 验收标准
- 普通模块修改能在较短时间内得到对应测试结果。
- 渲染输出、资产格式或性能发生非预期变化时CI 能够发现。
- 每个正式支持平台至少有构建、启动和最小场景加载验证。
## P1主干闭环后需要补强的能力
### 6. 物理与空间查询
需要引入可替换的物理模块边界,并逐步提供:
- [ ] Collider、RigidBody、Trigger 和 Physics Material。
- [ ] Raycast、Shape Cast、Overlap Query。
- [ ] 固定时间步、碰撞层和碰撞矩阵。
- [ ] 约束、角色控制器和物理调试视图。
- [ ] 场景序列化、Prefab、脚本回调和 Player 集成。
第一阶段若只展示静态数字孪生场景,可以延后刚体模拟,但空间查询和触发交互通常应优先实现。
### 7. 渲染生产力
需要补齐:
- [ ] Directional、Point、Spot Light 的完整组件语义。
- [ ] 阴影开关、级联、质量、偏移和性能配置。
- [ ] 天空盒、IBL、反射探针和环境配置资产化。
- [ ] 后处理 Profile曝光、色调映射、Bloom、抗锯齿等。
- [ ] Shader/Material 扩展工作流,而不只依赖固定 PBR 参数。
- [ ] LOD Group、实例化、遮挡剔除和大型场景可见性策略。
- [ ] 粒子、Line/Trail、Decal、Terrain 等常用表现组件。
- [ ] Overdraw、Wireframe、法线、光照和性能调试视图。
### 8. 动画系统
当前 Clip 播放基础之上,需要逐步增加:
- [ ] Animator Controller 和状态机。
- [ ] Cross Fade、Blend Tree、Layer 和 Mask。
- [ ] 骨骼 Avatar、动画重定向和 Root Motion。
- [ ] 动画事件、参数驱动和脚本控制 API。
- [ ] 动画预览、时间轴和状态调试器。
- [ ] 机械装配动画和 Timeline 类编排能力。
### 9. UI 系统
需要补齐:
- [ ] 稳定的 Button、Image、Text、Input、List、Scroll 等控件库。
- [ ] UI 事件、焦点、键盘导航和输入消费规则。
- [ ] Anchor、缩放、DPI、宽高比和多分辨率适配。
- [ ] 主题、字体、图集、本地化和基础动效。
- [ ] UI 到 RuntimeData、脚本和场景对象的统一数据绑定。
- [ ] 编辑器预览与 Player 渲染的一致性测试。
### 10. Scene 与 Prefab
需要补齐:
- [ ] 多场景编辑、Additive Load、SubScene 和异步流式加载。
- [ ] 大场景分区、按需加载和场景依赖检查。
- [ ] 嵌套 Prefab、Prefab Variant 和实例同步。
- [ ] 细粒度 Override 记录、可视化、Apply 和 Revert。
- [ ] 跨场景对象引用规则。
- [ ] Scene、Component、Prefab 的格式版本迁移机制。
### 11. 编辑器生产力与插件边界
需要补齐:
- [ ] 完整 Game View 与分辨率/输入模拟。
- [ ] 通用资源拖拽、上下文菜单、搜索和命令面板。
- [ ] 布局持久化、崩溃恢复和场景自动保存。
- [ ] 项目模板、创建向导、Build Settings 和平台设置面板。
- [ ] 稳定的插件 SDK、扩展点、版本检查和插件隔离。
- [ ] 大型 Hierarchy、Project Browser 和 Inspector 的性能优化。
## P2按产品需要扩展
这些属于成熟通用引擎常见能力,但不应阻塞第一阶段工业 C/S 交付:
- 音频资源、3D 音频、混音器和流式播放。
- NavMesh、寻路、动态避障和导航调试。
- 通用网络复制、RPC、会话和预测。
- Terrain、植被、复杂粒子和电影级 Timeline。
- OpenXR、VR 交互和设备适配。
- WebGPU、浏览器资源加载和 Web 发布。
- 多人协作编辑、资源锁和版本控制深度集成。
- 可视化脚本、Shader Graph 和高级内容创作工具。
## 代码结构风险
当前代码虽已建立服务接口和模块机制,但大型实现仍然过度集中:
- `MetaCoreBuiltinCoreServicesModule.cpp` 约 1 万行。
- `MetaCoreBuiltinEditorModule.cpp` 约 9500 行。
- `MetaCoreEditorApp.cpp` 约 3000 行。
- `MetaCoreSmokeTests.cpp` 约 5500 行。
继续在这些文件中横向增加功能,会逐步放大编译时间、合并冲突、测试隔离和模块替换成本。
需要做的事:
- [ ] 按 Asset、Prefab、Script、PlayMode、Material、UI、Build 等职责拆分实现文件。
- [ ] 保留现有服务接口,把注册组合留在 Builtin Module 中。
- [ ] 将无 UI 的业务逻辑从 ImGui Panel 中抽离为可测试服务。
- [ ] 避免 Editor 直接承载 Player 所需的通用运行时逻辑。
- [ ] 建立清晰依赖方向,防止 Foundation、Scene、Runtime 反向依赖 Editor。
- [ ] 为资源处理、Cook 和后台任务建立统一 Job/Task 抽象。
## 推荐执行顺序
```text
R1可交付闭环
Build Profile -> Stage/Package -> clean-machine 验证 -> 崩溃诊断
R2生产级内容管线
Import Settings -> 异步导入 -> 缩略图/缓存 -> 引用检查与修复
R3应用逻辑与交互
用户脚本工程 -> 时间/事件 -> Input Map -> UI/3D 事件路由
R4表现与内容复用
UI 强化 -> 灯光/阴影/后处理 -> 动画状态机 -> Prefab/Scene 强化
R5工程化与平台化
测试拆分 -> 性能回归 -> 平台 CI -> 长稳与资源泄漏验证
R6按项目需求扩展
物理 -> 音频 -> 导航 -> XR/Web -> 其他通用引擎能力
```
其中 R1、R2、R3 是 MetaCore 从“可展示原型”进入“真实项目生产”的关键,不应被 P2 功能插队。
## 阶段完成定义
当以下条件全部成立时,可以认为 MetaCore 已从早期 Alpha 进入可交付基础版本:
- [ ] 用户可以创建项目、导入正式支持的模型和编辑场景。
- [ ] 材质、动画、UI、Prefab 和用户行为能够保存、Cook 并在 Player 中运行。
- [ ] 项目可以通过统一命令生成不依赖开发环境的交付包。
- [ ] 同一项目在 Editor Play Mode 和独立 Player 中的核心行为一致。
- [ ] 资产重导入、移动和重命名不会静默破坏引用。
- [ ] 脚本、资产和运行时配置错误均有可定位的诊断信息。
- [ ] 目标平台具备自动构建、启动、最小渲染和长稳验证。
- [ ] 关键工作流有分层测试、黄金资产回归和性能基线保护。
## 与既有计划的关系
本文是现状评估和后续任务入口,不替代已有专项设计:
- 产品范围仍以 `metacore-product-plan.md``metacore-phase1-scope.md` 为准。
- 现有 M1-M4 文档记录第一阶段原始里程碑,但其中部分状态已落后于当前代码。
- 发布、UI、模型和材质的详细实现继续参考对应专项设计。
- 后续更新 Roadmap 时,应优先把本文 P0 项合并进正式里程碑和验收矩阵。

View File

@ -4282,6 +4282,8 @@ void MetaCoreTestUiDocumentSerialization() {
document.Name = "MainHud";
document.ReferenceWidth = 1920;
document.ReferenceHeight = 1080;
document.BackgroundColor = glm::vec3(0.02F, 0.04F, 0.08F);
document.BackgroundAlpha = 0.65F;
document.RootNodeIds = {"root.panel"};
MetaCore::MetaCoreUiNodeDocument rootNode;
@ -4315,6 +4317,9 @@ void MetaCoreTestUiDocumentSerialization() {
buttonNode.ParentId = "root.panel";
buttonNode.Text = "Start";
buttonNode.Interactable = true;
buttonNode.Action = "hud.start";
buttonNode.DataBinding = "game.session.state";
buttonNode.DataFormat = "State: {0}";
buttonNode.RectTransform.AnchorMin = glm::vec3(1.0F, 0.0F, 0.0F);
buttonNode.RectTransform.AnchorMax = glm::vec3(1.0F, 0.0F, 0.0F);
buttonNode.RectTransform.Pivot = glm::vec3(1.0F, 0.0F, 0.0F);
@ -4336,6 +4341,8 @@ void MetaCoreTestUiDocumentSerialization() {
MetaCoreExpect(roundTrip.Name == document.Name, "UiDocument 名称应保持");
MetaCoreExpect(roundTrip.ReferenceWidth == 1920, "UiDocument 参考宽度应保持");
MetaCoreExpect(roundTrip.ReferenceHeight == 1080, "UiDocument 参考高度应保持");
MetaCoreExpectVec3Near(roundTrip.BackgroundColor, glm::vec3(0.02F, 0.04F, 0.08F), "UiDocument 背景色应保持");
MetaCoreExpect(std::abs(roundTrip.BackgroundAlpha - 0.65F) <= 0.0001F, "UiDocument 背景透明度应保持");
MetaCoreExpect(roundTrip.RootNodeIds.size() == 1, "UiDocument 根节点数应保持");
MetaCoreExpect(roundTrip.Nodes.size() == 3, "UiDocument 节点数量应保持");
MetaCoreExpect(roundTrip.Nodes[0].Children.size() == 2, "UiNode 子节点列表应保持");
@ -4343,8 +4350,79 @@ void MetaCoreTestUiDocumentSerialization() {
MetaCoreExpect(roundTrip.Nodes[1].Text == "MetaCore", "Ui Text 内容应保持");
MetaCoreExpect(roundTrip.Nodes[1].Style.FontSize == 28.0F, "Ui 字体大小应保持");
MetaCoreExpect(roundTrip.Nodes[2].Interactable, "Ui Button 可交互状态应保持");
MetaCoreExpect(roundTrip.Nodes[2].Action == "hud.start", "Ui Button Action 应保持");
MetaCoreExpect(roundTrip.Nodes[2].DataBinding == "game.session.state", "Ui 数据绑定字段应保持");
MetaCoreExpect(roundTrip.Nodes[2].DataFormat == "State: {0}", "Ui 数据格式字段应保持");
MetaCoreExpect(roundTrip.Nodes[2].Style.HorizontalAlignment == MetaCore::MetaCoreUiHorizontalAlignment::Left, "默认水平对齐应保持");
MetaCoreExpectVec3Near(roundTrip.Nodes[0].Style.BackgroundColor, glm::vec3(0.05F, 0.08F, 0.12F), "Ui 背景色应保持");
const std::filesystem::path uiJsonPath = std::filesystem::temp_directory_path() / "MetaCoreUiDocumentSerialization.mcui.json";
MetaCoreExpect(
MetaCore::MetaCoreSceneSerializer::SaveUiToJson(uiJsonPath, document, registry),
"UiDocument JSON 应能保存"
);
const auto jsonRoundTrip = MetaCore::MetaCoreSceneSerializer::LoadUiFromJson(uiJsonPath, registry);
std::error_code removeError;
std::filesystem::remove(uiJsonPath, removeError);
MetaCoreExpect(jsonRoundTrip.has_value(), "UiDocument JSON 应能读取");
MetaCoreExpectVec3Near(jsonRoundTrip->BackgroundColor, glm::vec3(0.02F, 0.04F, 0.08F), "UiDocument JSON 背景色应保持");
MetaCoreExpect(std::abs(jsonRoundTrip->BackgroundAlpha - 0.65F) <= 0.0001F, "UiDocument JSON 背景透明度应保持");
MetaCoreExpect(jsonRoundTrip->Nodes.size() == 3, "UiDocument JSON 节点数量应保持");
MetaCoreExpect(jsonRoundTrip->Nodes[2].Action == "hud.start", "UiDocument JSON Action 应保持");
MetaCoreExpect(jsonRoundTrip->Nodes[2].DataBinding == "game.session.state", "UiDocument JSON 数据绑定应保持");
MetaCoreExpect(jsonRoundTrip->Nodes[2].DataFormat == "State: {0}", "UiDocument JSON 数据格式应保持");
const std::filesystem::path legacyUiJsonPath = std::filesystem::temp_directory_path() / "MetaCoreLegacyUiDocument.mcui.json";
{
std::ofstream legacyFile(legacyUiJsonPath, std::ios::trunc);
MetaCoreExpect(legacyFile.is_open(), "应能写入旧 UiDocument JSON");
legacyFile
<< "{\n"
<< " \"Name\": \"LegacyHud\",\n"
<< " \"ReferenceWidth\": 1280,\n"
<< " \"ReferenceHeight\": 720,\n"
<< " \"RootNodeIds\": [\"legacy.button\"],\n"
<< " \"Nodes\": [\n"
<< " {\n"
<< " \"Id\": \"legacy.button\",\n"
<< " \"Name\": \"Legacy Button\",\n"
<< " \"Type\": 3,\n"
<< " \"ParentId\": \"\",\n"
<< " \"Children\": [],\n"
<< " \"Visible\": true,\n"
<< " \"RectTransform\": {\n"
<< " \"AnchorMin\": [0, 0, 0],\n"
<< " \"AnchorMax\": [0, 0, 0],\n"
<< " \"Pivot\": [0.5, 0.5, 0],\n"
<< " \"Position\": [20, 30, 0],\n"
<< " \"Size\": [160, 48, 0]\n"
<< " },\n"
<< " \"Style\": {\n"
<< " \"BackgroundColor\": [0.1, 0.2, 0.3],\n"
<< " \"TextColor\": [1, 1, 1],\n"
<< " \"TintColor\": [1, 1, 1],\n"
<< " \"FontSize\": 18,\n"
<< " \"Padding\": [8, 8, 0],\n"
<< " \"HorizontalAlignment\": 0,\n"
<< " \"VerticalAlignment\": 0,\n"
<< " \"ImageAssetGuid\": \"\",\n"
<< " \"PreserveAspect\": true\n"
<< " },\n"
<< " \"Text\": \"Start\",\n"
<< " \"Interactable\": true\n"
<< " }\n"
<< " ]\n"
<< "}\n";
}
const auto legacyDocument = MetaCore::MetaCoreSceneSerializer::LoadUiFromJson(legacyUiJsonPath, registry);
std::filesystem::remove(legacyUiJsonPath, removeError);
MetaCoreExpect(legacyDocument.has_value(), "旧 UiDocument JSON 应能读取");
MetaCoreExpectVec3Near(legacyDocument->BackgroundColor, glm::vec3(0.0F, 0.0F, 0.0F), "旧 UiDocument 缺省背景色应为空黑");
MetaCoreExpect(std::abs(legacyDocument->BackgroundAlpha) <= 0.0001F, "旧 UiDocument 缺省背景应透明");
MetaCoreExpect(legacyDocument->Nodes.size() == 1, "旧 UiDocument JSON 节点数量应保持");
MetaCoreExpect(legacyDocument->Nodes.front().Action.empty(), "旧 UiDocument 缺省 Action 应为空");
MetaCoreExpect(legacyDocument->Nodes.front().DataBinding.empty(), "旧 UiDocument 缺省数据绑定应为空");
MetaCoreExpect(legacyDocument->Nodes.front().DataFormat.empty(), "旧 UiDocument 缺省格式应为空");
}
void MetaCoreTestUiRendererComponentSnapshotAndJson() {
@ -4382,12 +4460,10 @@ void MetaCoreTestUiRendererComponentSnapshotAndJson() {
MetaCore::MetaCoreSceneRenderSync renderSync;
const MetaCore::MetaCoreSceneRenderSyncSnapshot hiddenRenderSnapshot = renderSync.BuildSnapshot(scene);
MetaCoreExpect(hiddenRenderSnapshot.Renderables.empty(), "隐藏的 3D UI Renderer 不应生成预览 Renderable");
MetaCoreExpect(hiddenRenderSnapshot.Renderables.empty(), "隐藏的 3D UI Renderer 不应生成 Renderable");
scene.FindGameObject(uiObject.GetId()).GetComponent<MetaCore::MetaCoreUiRendererComponent>().Visible = true;
const MetaCore::MetaCoreSceneRenderSyncSnapshot renderSnapshot = renderSync.BuildSnapshot(scene);
MetaCoreExpect(renderSnapshot.Renderables.size() == 1, "可见的 3D UI Renderer 应生成一个预览 Renderable");
MetaCoreExpect(renderSnapshot.Renderables.front().ObjectId == uiObject.GetId(), "UI 预览 Renderable 应映射回 UI 对象");
MetaCoreExpect(renderSnapshot.Renderables.front().BuiltinMesh == MetaCore::MetaCoreBuiltinMeshType::Cube, "UI 预览应使用内置 Cube 面板");
MetaCoreExpect(renderSnapshot.Renderables.empty(), "可见的 3D UI Renderer 不应生成占位 Renderable");
MetaCore::MetaCoreSceneDocument sceneDocument;
sceneDocument.Name = "UiRendererScene";