77 lines
4.3 KiB
Plaintext
77 lines
4.3 KiB
Plaintext
// 世界空间 UI:普通 Endor 树先在独立 UI 分辨率中布局/绘制,再作为 Panel3D 平面的纹理参与三维深度。
|
||
// 面板内还有 CanvasViewport,输入需经外层射线、面板 UV 和内层二维相机逐层转换。
|
||
// 面板内容仍是完整 Endor UI,可共享对象、绑定、源视觉协议与嵌套视口。
|
||
// 分别持有外层三维视口、面板变换和面板内二维画布,避免一次动作同时驱动多层相机。
|
||
class WorldActions {
|
||
// 三个引用分别指向观察相机所在视口、三维面板实体和内层二维视口,不把它们的坐标系混用。
|
||
view: Viewport3D
|
||
panel: Panel3D
|
||
canvas: CanvasViewport
|
||
// 重置三维相机位置与观察目标,不改变面板或内层画布业务状态。
|
||
// 不重建 Camera,也不复位其 fov/near/far/up 或 controller;reset 仅写下面显式列出的六个属性。
|
||
function reset(self: WorldActions): Unit {
|
||
// reset.onClick 派发零参绑定方法,按绝对值恢复外层相机位置/target;不会调用 canvas.fitView。
|
||
self.view.camera.x = 0
|
||
self.view.camera.y = 0.5
|
||
self.view.camera.z = 7
|
||
self.view.camera.targetX = 0
|
||
self.view.camera.targetY = 0
|
||
self.view.camera.targetZ = 0
|
||
}
|
||
// 只旋转 Panel3D 平面,面板内 UI 树仍用自己的逻辑宽高。
|
||
// turn.onClick 调用,每次给面板世界变换增加 15 度 Y 旋转,不修改 UI 分辨率或子树字段。
|
||
function turn(self: WorldActions): Unit { self.panel.rotate(0, 15, 0) }
|
||
// 找到画布当前 Mount,按该出现位置的有限尺寸 fitView,而非用外层三维视口大小。
|
||
// fit.onClick 在挂载后调用;本例 canvas 只在一个面板出现,at(0) 选择其有效 Mount,再按该出现计算相机适配。
|
||
function fit(self: WorldActions): Unit { self.canvas.mounts().at(0).fitView() }
|
||
}
|
||
count = ui.state(0)
|
||
increment = Button("点击:世界里的真实按钮")
|
||
increment.onClick(count.increment)
|
||
// 这个按钮在 Panel3D 内,后面的 reset/turn/fit 在外层页面;它们共享 VM,但输入需要不同坐标转换路径。
|
||
// 二维场景卡片使用带符号世界坐标;canvas 再提供有限可见区域和独立 camera。
|
||
card = Column(10, 6, TextBlock("CanvasViewport", 1), RectBlock(180, 26, 6, "#9CB8A8"))
|
||
card.boxWidth = 240
|
||
card.x = 10
|
||
card.y = 4
|
||
canvas = CanvasViewport(280, 80, Scene2D(card))
|
||
// 普通 Column、文字、按钮和二维画布组成 UI 树,按钮仍修改同一个 count。
|
||
ui_root = Column(8, 8, TextBlock("世界空间 UI", 2), ui.textOf(count, "value", 2), increment, canvas)
|
||
ui_root.crossAlign = "stretch"
|
||
// 4x3 是三维世界尺寸,320x240 是 UI 布局/纹理分辨率;两者独立控制显示比例与清晰度。
|
||
panel = Panel3D(4, 3, ui_root)
|
||
panel.uiWidth = 320
|
||
panel.uiHeight = 240
|
||
// panel 的 x/rotationY 改三维平面变换;UI 仍在 320×240 的逻辑区域里布局,不把世界单位当作字形像素。
|
||
panel.x = -1.2
|
||
panel.rotationY = 15
|
||
cube = Box3D(1.4, 1.4, 1.4, "#7B9CB7")
|
||
cube.x = 2
|
||
cube.rotationY = 25
|
||
floor = Box3D(10, 0.08, 5, "#D8DEE6")
|
||
floor.y = -1.8
|
||
// 面板与普通网格/地面共处一个深度场景,实体可遮挡面板,也阻挡被遮住的 UI 命中。
|
||
scene = Scene3D(panel, cube, floor, DirectionalLight(-1, -2, -3, "#FFFFFF", 0.9))
|
||
scene.ambient = 0.35
|
||
// 外层 Viewport3D 作为二维布局里的 flex 子项;相机控制外部观察,而非 UI 内部滚动。
|
||
view = Viewport3D(960, 520, scene)
|
||
view.flex = 1
|
||
view.camera.y = 0.5
|
||
view.camera.z = 7
|
||
// 控制器保存已创建的三层对象,注册回调不会提前执行重置、转动或 Mount 查询。
|
||
actions = WorldActions(view, panel, canvas)
|
||
reset = Button("重置摄像机")
|
||
reset.onClick(actions.reset)
|
||
turn = Button("转动 UI 面板")
|
||
turn.onClick(actions.turn)
|
||
fit = Button("适配面板内的画布")
|
||
fit.onClick(actions.fit)
|
||
// 控制按钮保持在二维页面空间,外层三维内容作为一个区域随布局伸展。
|
||
body = Column(24, 16, TextBlock("UI → 3D 世界 → UI → Canvas", 2), view, Row(0, 12, reset, turn, fit), TextBlock("空白处左键拖动旋转 3D · 中键/空格平移 · 滚轮缩放\n面板里的按钮、文本、布局和画布都由同一份 Endor 对象驱动。"))
|
||
body.flex = 1
|
||
body.crossAlign = "stretch"
|
||
page = Page()
|
||
page.title = "世界空间 UI"
|
||
page.crossAlign = "stretch"
|
||
page.render(body)
|