UI系统更新

This commit is contained in:
Rowland 2026-06-29 09:55:59 +08:00
parent dcd4af87a2
commit 75d5fed693
7 changed files with 129 additions and 6 deletions

View File

@ -2850,6 +2850,7 @@ void MetaCoreDrawUiRendererComponentInspector(MetaCoreEditorContext& editorConte
};
drawBoolField("可见", &MetaCoreUiRendererComponent::Visible);
drawBoolField("接收输入", &MetaCoreUiRendererComponent::InputEnabled);
drawBoolField("始终朝向相机", &MetaCoreUiRendererComponent::FaceCamera);
int layer = MetaCoreGetSharedSelectedValue<std::int32_t>(
editorContext,

View File

@ -5,6 +5,7 @@
#include "MetaCoreFoundation/MetaCorePackage.h"
#include "MetaCoreFoundation/MetaCoreProject.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include "MetaCoreScene/MetaCoreTransformUtils.h"
#include "MetaCoreRender/MetaCoreSceneRenderSync.h"
#include "MetaCoreEditorCameraController.h"
@ -28,6 +29,7 @@
#define GLM_ENABLE_EXPERIMENTAL
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdlib>
@ -1103,6 +1105,88 @@ void MetaCoreDrawUiDocumentPreview(
drawList.PopClipRect();
}
[[nodiscard]] bool MetaCoreProjectUiLocalPoint(
const MetaCoreSceneView& sceneView,
const MetaCoreSceneViewportState& viewportState,
const glm::mat4& worldMatrix,
const glm::vec2& localPoint,
ImVec2& screenPoint
) {
const glm::vec3 worldPoint = glm::vec3(worldMatrix * glm::vec4(localPoint.x, localPoint.y, 0.0F, 1.0F));
return MetaCoreProjectWorldPointToViewport(sceneView, viewportState, worldPoint, screenPoint);
}
[[nodiscard]] bool MetaCoreProjectUiQuad(
const MetaCoreSceneView& sceneView,
const MetaCoreSceneViewportState& viewportState,
const glm::mat4& worldMatrix,
const glm::vec2& minPoint,
const glm::vec2& maxPoint,
std::array<ImVec2, 4>& screenPoints
) {
return MetaCoreProjectUiLocalPoint(sceneView, viewportState, worldMatrix, glm::vec2(minPoint.x, minPoint.y), screenPoints[0]) &&
MetaCoreProjectUiLocalPoint(sceneView, viewportState, worldMatrix, glm::vec2(maxPoint.x, minPoint.y), screenPoints[1]) &&
MetaCoreProjectUiLocalPoint(sceneView, viewportState, worldMatrix, glm::vec2(maxPoint.x, maxPoint.y), screenPoints[2]) &&
MetaCoreProjectUiLocalPoint(sceneView, viewportState, worldMatrix, glm::vec2(minPoint.x, maxPoint.y), screenPoints[3]);
}
void MetaCoreDrawProjectedUiDocumentPreview(
ImDrawList& drawList,
const MetaCoreUiDocument& document,
const MetaCoreSceneView& sceneView,
const MetaCoreSceneViewportState& viewportState,
const glm::mat4& worldMatrix
) {
const float referenceWidth = static_cast<float>(std::max(1, document.ReferenceWidth));
const float referenceHeight = static_cast<float>(std::max(1, document.ReferenceHeight));
for (const MetaCoreUiNodeDocument& node : document.Nodes) {
if (!node.Visible) {
continue;
}
const float left = node.RectTransform.Position.x / referenceWidth - 0.5F;
const float right = (node.RectTransform.Position.x + node.RectTransform.Size.x) / referenceWidth - 0.5F;
const float top = 0.5F - node.RectTransform.Position.y / referenceHeight;
const float bottom = 0.5F - (node.RectTransform.Position.y + node.RectTransform.Size.y) / referenceHeight;
std::array<ImVec2, 4> points{};
if (!MetaCoreProjectUiQuad(sceneView, viewportState, worldMatrix, glm::vec2(left, top), glm::vec2(right, bottom), points)) {
continue;
}
const bool isText = node.Type == MetaCoreUiNodeType::Text;
const bool isButton = node.Type == MetaCoreUiNodeType::Button;
const bool isImage = node.Type == MetaCoreUiNodeType::Image;
if (!isText) {
drawList.AddConvexPolyFilled(points.data(), static_cast<int>(points.size()), MetaCoreUiPreviewColor(node.Style.BackgroundColor, isButton ? 0.92F : 0.82F));
}
drawList.AddPolyline(
points.data(),
static_cast<int>(points.size()),
isButton ? IM_COL32(130, 190, 255, 220) : IM_COL32(132, 145, 166, isText ? 90 : 170),
ImDrawFlags_Closed,
isButton ? 1.4F : 1.0F
);
if (isImage) {
drawList.AddLine(points[0], points[2], IM_COL32(210, 220, 235, 115), 1.0F);
drawList.AddLine(points[1], points[3], IM_COL32(210, 220, 235, 115), 1.0F);
}
const std::string label = !node.Text.empty() ? node.Text : (!node.Name.empty() ? node.Name : node.Id);
if (!label.empty()) {
ImVec2 textPoint{};
const glm::vec2 textLocal(
left + std::max(3.0F, node.Style.Padding.x) / referenceWidth,
top - std::max(3.0F, node.Style.Padding.y) / referenceHeight
);
if (MetaCoreProjectUiLocalPoint(sceneView, viewportState, worldMatrix, textLocal, textPoint)) {
drawList.AddText(textPoint, MetaCoreUiPreviewColor(node.Style.TextColor, 0.96F), label.c_str());
}
}
}
}
void MetaCoreDrawScreenSpaceUiPreviewOverlay(
const MetaCoreEditorContext& editorContext,
const MetaCoreScene& scene,
@ -1222,6 +1306,42 @@ void MetaCoreDrawWorldSpaceUiPreviewOverlay(
}
const MetaCoreTransformComponent& transform = gameObject.GetComponent<MetaCoreTransformComponent>();
const bool selected = editorContext.GetSelectedObjectId() == gameObject.GetId();
const ImU32 fillColor = selected ? IM_COL32(44, 136, 110, 48) : IM_COL32(44, 136, 110, 30);
const ImU32 strokeColor = selected ? IM_COL32(104, 224, 188, 230) : IM_COL32(104, 224, 188, 150);
const std::optional<MetaCoreUiDocument> document = MetaCoreLoadUiPreviewDocument(editorContext, uiRenderer);
const std::string label = "3D UI: " + gameObject.GetName();
if (!uiRenderer.FaceCamera) {
const glm::mat4 worldMatrix = MetaCoreBuildTransformMatrix(transform);
std::array<ImVec2, 4> panelPoints{};
if (!MetaCoreProjectUiQuad(
sceneView,
viewportState,
worldMatrix,
glm::vec2(-0.5F, 0.5F),
glm::vec2(0.5F, -0.5F),
panelPoints
)) {
continue;
}
drawList->AddConvexPolyFilled(panelPoints.data(), static_cast<int>(panelPoints.size()), fillColor);
drawList->AddPolyline(panelPoints.data(), static_cast<int>(panelPoints.size()), strokeColor, ImDrawFlags_Closed, selected ? 2.0F : 1.2F);
if (document.has_value()) {
MetaCoreDrawProjectedUiDocumentPreview(*drawList, *document, sceneView, viewportState, worldMatrix);
} else {
drawList->AddText(ImVec2(panelPoints[0].x + 8.0F, panelPoints[0].y + 27.0F), IM_COL32(255, 196, 120, 230), "未能读取 UI 文档");
}
drawList->AddText(ImVec2(panelPoints[0].x + 8.0F, panelPoints[0].y + 7.0F), strokeColor, label.c_str());
ImVec2 centerPoint{};
if (MetaCoreProjectWorldPointToViewport(sceneView, viewportState, transform.Position, centerPoint)) {
drawList->AddCircleFilled(centerPoint, 3.0F, strokeColor, 12);
}
continue;
}
ImVec2 centerPoint{};
if (!MetaCoreProjectWorldPointToViewport(sceneView, viewportState, transform.Position, centerPoint)) {
continue;
@ -1237,7 +1357,6 @@ void MetaCoreDrawWorldSpaceUiPreviewOverlay(
}
pixelsPerWorldUnit = viewportState.Height / (2.0F * distanceAlongView * std::tan(fovRadians * 0.5F));
}
const float rawPreviewWidth = worldWidth * pixelsPerWorldUnit;
const float rawPreviewHeight = worldHeight * pixelsPerWorldUnit;
const float maxPreviewWidth = std::max(64.0F, viewportState.Width * 0.9F);
@ -1247,21 +1366,16 @@ void MetaCoreDrawWorldSpaceUiPreviewOverlay(
const float previewHeight = std::max(24.0F, rawPreviewHeight * fitScale);
const ImVec2 rectMin(centerPoint.x - previewWidth * 0.5F, centerPoint.y - previewHeight * 0.5F);
const ImVec2 rectMax(rectMin.x + previewWidth, rectMin.y + previewHeight);
const bool selected = editorContext.GetSelectedObjectId() == gameObject.GetId();
const ImU32 fillColor = selected ? IM_COL32(44, 136, 110, 48) : IM_COL32(44, 136, 110, 30);
const ImU32 strokeColor = selected ? IM_COL32(104, 224, 188, 230) : IM_COL32(104, 224, 188, 150);
drawList->AddRectFilled(rectMin, rectMax, fillColor, 4.0F);
drawList->AddRect(rectMin, rectMax, strokeColor, 4.0F, 0, selected ? 2.0F : 1.2F);
const std::optional<MetaCoreUiDocument> document = MetaCoreLoadUiPreviewDocument(editorContext, uiRenderer);
if (document.has_value()) {
MetaCoreDrawUiDocumentPreview(*drawList, *document, rectMin, ImVec2(previewWidth, previewHeight));
} else {
drawList->AddText(ImVec2(rectMin.x + 8.0F, rectMin.y + 27.0F), IM_COL32(255, 196, 120, 230), "未能读取 UI 文档");
}
const std::string label = "3D UI: " + gameObject.GetName();
drawList->AddText(ImVec2(rectMin.x + 8.0F, rectMin.y + 7.0F), strokeColor, label.c_str());
drawList->AddCircleFilled(centerPoint, 3.0F, strokeColor, 12);
}

View File

@ -179,6 +179,7 @@ private:
lhs.UiRenderer->RenderMode != rhs.UiRenderer->RenderMode ||
lhs.UiRenderer->Visible != rhs.UiRenderer->Visible ||
lhs.UiRenderer->InputEnabled != rhs.UiRenderer->InputEnabled ||
lhs.UiRenderer->FaceCamera != rhs.UiRenderer->FaceCamera ||
lhs.UiRenderer->Layer != rhs.UiRenderer->Layer ||
!MetaCoreNearlyEqual(lhs.UiRenderer->PixelsPerUnit, rhs.UiRenderer->PixelsPerUnit) ||
!MetaCoreNearlyEqualVec3(lhs.UiRenderer->WorldSize, rhs.UiRenderer->WorldSize)) {

View File

@ -169,6 +169,7 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo
else if (uiField.Name == "RenderMode") uiJson["RenderMode"] = static_cast<int>(ui.RenderMode);
else if (uiField.Name == "Visible") uiJson["Visible"] = ui.Visible;
else if (uiField.Name == "InputEnabled") uiJson["InputEnabled"] = ui.InputEnabled;
else if (uiField.Name == "FaceCamera") uiJson["FaceCamera"] = ui.FaceCamera;
else if (uiField.Name == "Layer") uiJson["Layer"] = ui.Layer;
else if (uiField.Name == "PixelsPerUnit") uiJson["PixelsPerUnit"] = ui.PixelsPerUnit;
else if (uiField.Name == "WorldSize") uiJson["WorldSize"] = Vec3ToJson(ui.WorldSize);
@ -337,6 +338,7 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
else if (uiField.Name == "RenderMode" && uiJson.contains("RenderMode")) ui.RenderMode = static_cast<MetaCoreUiRenderMode>(uiJson["RenderMode"].get<int>());
else if (uiField.Name == "Visible" && uiJson.contains("Visible")) ui.Visible = uiJson["Visible"].get<bool>();
else if (uiField.Name == "InputEnabled" && uiJson.contains("InputEnabled")) ui.InputEnabled = uiJson["InputEnabled"].get<bool>();
else if (uiField.Name == "FaceCamera" && uiJson.contains("FaceCamera")) ui.FaceCamera = uiJson["FaceCamera"].get<bool>();
else if (uiField.Name == "Layer" && uiJson.contains("Layer")) ui.Layer = uiJson["Layer"].get<std::int32_t>();
else if (uiField.Name == "PixelsPerUnit" && uiJson.contains("PixelsPerUnit")) ui.PixelsPerUnit = uiJson["PixelsPerUnit"].get<float>();
else if (uiField.Name == "WorldSize" && uiJson.contains("WorldSize")) ui.WorldSize = JsonToVec3(uiJson["WorldSize"]);

View File

@ -211,6 +211,8 @@ struct MetaCoreUiRendererComponent {
MC_PROPERTY()
bool InputEnabled = true;
MC_PROPERTY()
bool FaceCamera = false;
MC_PROPERTY()
std::int32_t Layer = 0;
MC_PROPERTY()
float PixelsPerUnit = 1000.0F;

View File

@ -4090,6 +4090,7 @@ void MetaCoreTestUiRendererComponentSnapshotAndJson() {
uiRenderer.RenderMode = MetaCore::MetaCoreUiRenderMode::WorldSpace;
uiRenderer.Visible = false;
uiRenderer.InputEnabled = false;
uiRenderer.FaceCamera = true;
uiRenderer.Layer = 42;
uiRenderer.PixelsPerUnit = 512.0F;
uiRenderer.WorldSize = glm::vec3(3.0F, 2.0F, 0.0F);
@ -4105,6 +4106,7 @@ void MetaCoreTestUiRendererComponentSnapshotAndJson() {
const auto& restoredRenderer = restoredObject.GetComponent<MetaCore::MetaCoreUiRendererComponent>();
MetaCoreExpect(restoredRenderer.UiDocumentAssetGuid == uiGuid, "恢复快照后 UI 资产 Guid 应一致");
MetaCoreExpect(restoredRenderer.RenderMode == MetaCore::MetaCoreUiRenderMode::WorldSpace, "恢复快照后渲染模式应一致");
MetaCoreExpect(restoredRenderer.FaceCamera, "恢复快照后朝向相机设置应一致");
MetaCoreExpect(restoredRenderer.Layer == 42, "恢复快照后层级应一致");
MetaCoreExpectVec3Near(restoredRenderer.WorldSize, glm::vec3(3.0F, 2.0F, 0.0F), "恢复快照后世界尺寸应一致");
@ -4133,6 +4135,7 @@ void MetaCoreTestUiRendererComponentSnapshotAndJson() {
MetaCoreExpect(loadedRenderer.RenderMode == MetaCore::MetaCoreUiRenderMode::WorldSpace, "JSON 往返后渲染模式应一致");
MetaCoreExpect(!loadedRenderer.Visible, "JSON 往返后可见状态应一致");
MetaCoreExpect(!loadedRenderer.InputEnabled, "JSON 往返后输入状态应一致");
MetaCoreExpect(loadedRenderer.FaceCamera, "JSON 往返后朝向相机设置应一致");
MetaCoreExpect(loadedRenderer.Layer == 42, "JSON 往返后层级应一致");
MetaCoreExpect(std::abs(loadedRenderer.PixelsPerUnit - 512.0F) <= 0.0001F, "JSON 往返后 PPU 应一致");
MetaCoreExpectVec3Near(loadedRenderer.WorldSize, glm::vec3(3.0F, 2.0F, 0.0F), "JSON 往返后世界尺寸应一致");