网易自定义容器 UI 制作工作流
本技能把 echo_chest 模组的自定义容器制作流程抽象成可复用步骤,重点覆盖资源包 UI、客户端 ScreenProxy、服务端容器事件和常见对齐问题。
适用场景
- 新做一个基于
netease:block_container的自定义方块容器。 - 复刻“自定义箱子 + 原版背包 + 自定义槽位/进度条”的 UI。
- 排查容器打不开、UI 不加载、格子错位、服务端容器事件不触发、进度条不同步。
- 调整容器槽位布局、保留特殊槽位、给槽位叠加图标或动态控件。
- 从 Java 版整张 GUI 贴图迁移到网易/基岩 JSON UI,处理背景拼接、箭头进度条裁剪、空槽提示图显隐。
可用资源
- UI JSON 模板:
./assets/container-ui.template.json - 客户端 ScreenProxy 模板:
./assets/client-screen-proxy.template.py - 客户端注册模板:
./assets/client-listen.template.py - 服务端容器事件模板:
./assets/server-container.template.py - 排错清单:
./references/troubleshooting.md - Java GUI 迁移与空槽提示图显隐参考:
./references/java-gui-porting-and-empty-slot.md
先检查的项目文件
参考当前项目时,优先看这些文件:
- 方块容器声明:
behavior_pack_*/netease_blocks/<block>.json - UI 注册表:
resource_pack_*/ui/_ui_defs.json - 容器 UI:
resource_pack_*/ui/<container>.json - 客户端注册与打开状态:
behavior_pack_*/**/client/*Listen.py - ScreenProxy:
behavior_pack_*/**/client/ui/*Screen.py - 服务端容器逻辑:
behavior_pack_*/**/server/*Listen.py - 常量路径:
behavior_pack_*/**/modConfig.py
总体流程
- 声明容器方块:在行为包方块 JSON 中设置
description.base_block为netease_container,并添加netease:block_container。 - 绑定容器界面:
netease:block_container.screen_name必须等于 UI 主屏幕名,例如echo_chest.EchoChestMain。 - 编写资源包 UI:用
common.inventory_screen_common承接原版容器能力,并在$screen_content中放自定义面板。 - 注册 UI 文件:把
ui/<name>.json加进资源包ui/_ui_defs.json。 - 注册 ScreenProxy:客户端系统初始化时用
NativeScreenManager.instance().RegisterScreenProxy(screen_name, proxy_path)。 - 在 ScreenProxy 中接管界面:
OnCreate里拿ScreenNode,通知客户端系统记录打开状态;OnDestroy里清理并通知服务端关闭。 - 服务端处理容器事件:监听
ItemPushInCustomContainerServerEvent、ItemPullOutCustomContainerServerEvent、PlayerTryPutCustomContainerItemServerEvent等事件校验槽位和物品。 - 双端同步动态状态:服务端保存方块实体数据,按需
CallClient同步 UI 数据;客户端在OnTick或回调中更新进度条、动画和 Molang。
行为包方块 JSON 要点
在 minecraft:block.components 中配置:
netease:block_container.container_size:容器总槽位数,必须覆盖 UI 中要展示的槽位数。netease:block_container.custom_description:服务端容器事件里的collectionName常用这个值判断容器来源。netease:block_container.screen_name:必须精确匹配资源包 UI 的namespace.screen。netease:block_entity.tick:需要自动吸物品、经验、更新数据时设为true。
完成检查:container_size、UI maximum_grid_items、服务端可访问槽位范围三者一致;特殊槽位要在服务端单独限制。
UI JSON 制作重点
1. 主屏幕入口
推荐从原版容器模板继承:
- 定义
<ScreenName>@common.inventory_screen_common。 - 在
variables中给桌面端/移动端设置$screen_content。 $screen_content指向自己的主面板,例如<namespace>.<main_panel>。- 顶层要有
namespace。
关键完成条件:screen_name 写成 namespace + "." + 主屏幕控件名。
1.1 Java GUI 贴图迁移到基岩 UI
Java 版容器常把完整背景、槽位、箭头、提示图画在同一张 GUI PNG 上;基岩/网易 JSON UI 不应直接把这张整图作为背景。迁移时按功能拆分:
- 背景面板:用原版/网易 UI 背景拼接,例如
textures/ui/dialog_background_opaque、common.inventory_panel_bottom_half_with_label、common.hotbar_grid_template。 - 槽位:用
common.container_item或静态collection_panel/grid生成真实容器槽位,不要依赖 Java 背景图里的假槽位。 - 进度条/箭头:从 Java GUI 中裁出空箭头底图和填充箭头图,分别作为
empty_progress_bar与filled_progress_bar的贴图;去掉整图背景后,空箭头也必须单独绘制。 - 提示图:从物品图或 Java GUI 裁成小图,放到
$cell_overlay_ref或脚本控制的 image 中,不能把提示图烙在背景上。
如果只是想还原 Java 截图布局,先记录 Java 像素坐标,再换算成 JSON UI 中的 offset/size。不要把 Java 整张 GUI PNG 当作基岩背景,否则会和原版背包面板、关闭按钮、槽位高亮重复叠加。
2. 容器格子 grid
常用结构:
type: "grid"collection_name: "netease_container"grid_item_template: "<namespace>.<grid_item>"grid_rescaling_type: "horizontal"maximum_grid_items: <container_size><grid_item>@common.container_item,并设置$item_collection_name: "netease_container"
注意:UI 里的 collection_name 通常保持 netease_container;服务端事件里的 collectionName 不一定是这个值,常按 custom_description 判断。
3. 布局结构
推荐结构:
common.root_panel承接安全区和输入逻辑。common.common_panel承接对话框背景。common.inventory_panel_bottom_half_with_label+common.hotbar_grid_template显示玩家背包和快捷栏。- 自定义容器区域用独立
image或panel包住:背景、容器 grid、标题、特殊进度条。 - 关闭按钮沿用
common.light_close_button/common.compact_close_button,避免重做关闭逻辑。
完成检查:自定义容器区域和玩家背包不要互相遮挡;layer 从背景到物品、按钮逐层递增。
布局排错经验:
- 如果最里层露出一块多余空白背景,检查是否用了
bg_image@$dialog_background。只需要承载控件时改成普通panel;需要背景时才继承$dialog_background。 - 自定义上方面板贴住玩家背包时,用面板高度计算偏移:例如玩家背包
offset=[0,48]、高度96,其顶边约在父面板中心;上方面板高度44时常用offset=[0,-22]让底边贴齐。 - 关闭按钮放到当前容器面板内,设置
anchor_from/top_right、anchor_to/top_right和小偏移,避免继承默认外层位置后跑偏。
4. 进度条与特殊控件
可用做法:
- 用
panel包一组netease_editor_template_namespace.empty_progress_bar和filled_progress_bar。 - 通过变量配置空槽贴图、填充贴图、裁剪方向、九宫格等。
- 在 ScreenProxy
OnTick中通过GetBaseUIControl(path).asProgressBar().SetValue(value)更新。
路径很长时,不要猜;先用 UI 结构确认路径,再集中写成常量或局部变量。动态生成的 grid 子项可能要等 UI 初始化完成后再操作。
进度条常见坑:
- 去掉 Java 整张背景图后,箭头底图会一起消失;必须单独提供空箭头贴图,必要时再加一个静态 image 兜底显示底图。
filled_progress_bar只负责填充裁剪,不等于会绘制空底图。- 动态进度控件要放在背景和槽位之上,
layer通常高于槽位背景,但低于关闭按钮。
4.1 空槽提示图显隐
优先参考原版盔甲/鞘翅槽和织布机槽的做法:
- 在槽位 item 上设置
$cell_overlay_ref,指向一个提示 image,例如namespace.fly_empty_image。 - 提示 image 自身绑定一个布尔值控制
#visible。 - 如果是原版 collection,可尝试
#empty_image_visible;但网易netease_container自定义容器不一定会给这个 binding 正确更新。 - 自定义容器更稳的方案是使用
ViewBinder.BF_BindBool:- 服务端读取真实容器槽位是否为空。
- 服务端通过
CallClient同步inputEmpty这类布尔状态。 - 客户端系统缓存状态。
- ScreenProxy 中用
@ViewBinder.binding(ViewBinder.BF_BindBool, "#xxx_visible")返回缓存值。 - UI image 绑定同一个
#xxx_visible。
不要把提示图放到 $background_images 背景层来“靠物品盖住”。这在半透明物品、同形状贴图或缩放差异下会露边,且不是真正隐藏。
需要可复制的 JSON/Python 片段时,读取 ./references/java-gui-porting-and-empty-slot.md。
5. 动态调整槽位或叠加图标
只有 grid 自动生成项难以静态控制时才用 ScreenProxy 延迟修正:
OnCreate后用短定时器延迟执行,等待 grid 子控件生成。- 通过完整控件路径拿到目标槽位。
- 调用
SetPosition调整位置。 - 用
CreateChildControl(template, name, parent, True)叠加原版模板图标。
客户端 Python 联动
客户端系统职责:
- 在初始化中注册 ScreenProxy。
- 监听
ClientBlockUseEvent,记录玩家打开的是哪个容器方块坐标。 - ScreenProxy
OnCreate回调到客户端系统,设置打开状态、播放开箱效果、通知服务端NotifyOpenChest。 - ScreenProxy
OnDestroy清理 UI 节点、播放关箱效果、通知服务端NotifyCloseChest。 - 接收服务端同步数据,例如进度值,再由 ScreenProxy 更新 UI。
- 需要动态显隐提示图时,在 ScreenProxy 中使用
ViewBinder绑定布尔值,不要只依赖#empty_image_visible。
边界要求:客户端只做 UI、声音、粒子、Molang 表现和请求;容器真实物品、方块实体数据、校验逻辑放服务端。
服务端容器逻辑
服务端系统职责:
- 记录每个玩家正在打开的容器坐标。
- 在容器事件中按
collectionName和collectionIndex做校验。 - 对特殊槽位做白名单/黑名单。
- 在
ServerBlockEntityTickEvent中读取/写入方块实体数据,处理自动加工、吸取、合堆、生成物品。 - 对正在查看该容器的玩家同步 UI 动态数据。
- 特殊槽位提示图的显隐状态应以服务端真实容器槽位为准,例如同步
{ "progress": 0.5, "inputEmpty": false }。
完成检查:客户端关闭 UI 时服务端记录必须清理,否则会持续推送旧数据。
验收清单
- 方块 JSON:
base_block、netease:block_container、screen_name、container_size正确。 - 资源包:UI JSON 存在且已写入
_ui_defs.json。 - UI:
namespace.screen、$screen_content、grid.collection_name、grid_item_template、maximum_grid_items一致。 - 客户端:ScreenProxy 已注册,
OnCreate/OnDestroy可追踪打开与关闭状态。 - 服务端:容器事件能收到,槽位校验只影响目标容器。
- 动态 UI:进度条、特殊槽位、图标叠加只在 UI 节点存在后操作。
- 空槽提示图:空槽时显示,放入物品后真正隐藏;如果是
netease_container,优先用服务端同步 +ViewBinder验证。 - Java 贴图迁移:UI 背景由基岩原版面板拼接,Java 整张 GUI 只作为裁剪小贴图来源。
- 双端:客户端表现和服务端数据职责分离,无跨端 API 混用。
- 运行前:用 JSON 解析检查资源包/行为包 JSON;Python 校验优先使用编辑器诊断,避免生成
.pyc。
使用方式
示例请求:
- “用
netease-custom-container-ui按 9 格容器做一个自定义箱子 UI。” - “用
netease-custom-container-ui检查这个容器为什么打开后不是我的 UI。” - “用
netease-custom-container-ui给第 24 槽加燃料槽限制和 UI 图标。” - “用
netease-custom-container-ui给自定义容器增加服务端同步进度条。”