UI优化
This commit is contained in:
parent
08d528c686
commit
278313f739
Binary file not shown.
@ -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);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
@ -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());
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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{};
|
||||
|
||||
|
||||
@ -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 项拆入具体任务卡和验收矩阵
|
||||
|
||||
## 专项设计文档
|
||||
|
||||
|
||||
339
docs/designs/metacore-mature-engine-gap-and-work-plan.md
Normal file
339
docs/designs/metacore-mature-engine-gap-and-work-plan.md
Normal 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 项合并进正式里程碑和验收矩阵。
|
||||
@ -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";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user