界面与交互
中国版《我的世界》提供了一套基于 JSON 声明式布局 + Python 脚本逻辑 的 UI 系统。界面由 JSON 文件描述控件树与属性,引擎负责渲染,Python 脚本负责交互逻辑与数据驱动。你可以用它制作 HUD、商店、背包、设置面板等各种游戏界面。
概述
| 层面 | 技术 |
|---|---|
| 布局与样式 | JSON 文件(声明控件树、属性、动画、数据绑定) |
| 渲染 | 引擎内置渲染管线,基于控件树自上而下绘制 |
| 逻辑 | Python 脚本(ScreenNode 子类),通过 Mod API 与 JSON 控件交互 |
| 编辑 | MC Studio 内置可视化界面编辑器,或手写 JSON |
关键特性:
- UI 贴图仅支持 PNG 格式
- 文件与控件命名仅支持 字母、数字、下划线
- 支持继承与模板复用,一个控件可以继承另一个控件的全部属性并重写部分属性
- 支持九宫格缩放、图片裁剪、旋转、平铺等高级图片特性
UI 文件结构
所有 UI 文件存放在资源包的 ui/ 目录下,格式为 JSON。
resource_pack/
├── textures/ui/ # UI 贴图(仅 PNG)
├── ui/
│ ├── _ui_defs.json # 注册所有 UI 文件
│ ├── _global_variables.json # 全局变量
│ └── my_ui.json # 自定义界面文件_ui_defs.json
所有自定义 UI 文件都必须在 _ui_defs.json 中注册,否则引擎无法找到:
{
"ui_defs": [
"ui/my_ui.json",
"ui/shop_screen.json"
]
}命名空间
每个 JSON 文件都有一个全局唯一的 namespace,用于跨文件引用控件。建议 namespace 与文件名保持一致:
{
"namespace": "my_ui",
"main": {
"type": "screen",
"controls": [
{ "title@my_ui.title_label": {} }
]
},
"title_label": {
"type": "label",
"text": "Hello World",
"color": [1.0, 1.0, 1.0]
}
}跨文件引用格式:控件名@命名空间.被引用控件,例如 mybutton@common.button。
控件类型
| 控件 | type 值 | 说明 |
|---|---|---|
| 画布 Screen | screen | 界面根节点,所有控件必须在画布内 |
| 面板 Panel | panel | 透明容器,用于分组与布局,无专属属性 |
| 图片 Image | image | 显示贴图,支持九宫格、裁剪、旋转、平铺 |
| 文本 Label | label | 显示文字,支持颜色、字号、对齐、行间距 |
| 按钮 Button | button | 可点击交互,支持默认/按下/悬浮三态贴图 |
| 输入框 EditBox | edit_box | 文字输入框 |
| 滚动视图 ScrollView | scroll_view | 可滚动的内容容器 |
| 网格 Grid | grid | 网格布局容器,常用于背包、列表 |
| 进度条 ProgressBar | progress_bar | 进度显示,基于图片裁剪实现 |
| 开关 Toggle | toggle | 勾选框/开关 |
| 滑条 Slider | slider | 数值滑动条 |
提示
panel 是最常用的布局容器——它本身不可见,仅用于归纳子控件,类似于 HTML 的 <div>。
布局系统
锚点(Anchor)九宫定位
每个控件通过两个锚点属性确定位置:
| 属性 | 说明 |
|---|---|
anchor_from | 子控件挂接到父控件的哪个定位点 |
anchor_to | 用自身的哪个定位点去挂接 |
9 个可选值:top_left、top_middle、top_right、left_middle、center、right_middle、bottom_left、bottom_middle、bottom_right。
当 anchor_from 与 anchor_to 相同时,控件的该定位点与父控件的该定位点重合(偏移为 0 时)。
尺寸(Size)
size 以 [宽, 高] 数组表示,支持多种单位混合写法:
| 写法 | 含义 |
|---|---|
100 或 "100px" | 固定 100 像素 |
"50%" | 父控件尺寸的 50% |
"50% + 10px" | 父控件 50% 再加 10 像素 |
"100%c" | 子控件总宽/高的 100% |
"100%sm" | 兄弟控件中最大宽/高的 100% |
"fill" | 填满父控件剩余空间 |
"default" | 默认值,等同于 "100%" |
偏移(Offset)
offset 的写法与 size 相同(% + px),表示相对锚点的位移:
- X 正值向右,负值向左
- Y 正值向下,负值向上
父子关系
控件通过 controls 数组嵌套子控件,形成树形结构。子控件的位置、尺寸百分比均相对于父控件计算。
"my_panel": {
"type": "panel",
"size": ["100%", "100%"],
"controls": [
{
"child_image": {
"type": "image",
"size": ["50%", "50%"],
"anchor_from": "center",
"anchor_to": "center",
"texture": "textures/ui/my_icon"
}
}
]
}层级与渲染顺序
控件树中,同级控件排列在下方的会渲染在上层(遮挡上方的控件)。这与 UI 编辑器中控件结构面板的顺序一致。
如果需要精确控制层级而不依赖排列顺序,可以使用 layer 属性:
layer值越大,渲染越靠前(越上层)- 最终显示层级 = 从根节点到当前节点的
layer累加值
注意
在 UI 编辑器中取消"自动设定层级"后才能手动编辑 layer,且一旦取消将无法重新勾选。
创建 HUD 界面
HUD 是常驻界面(如血条、准心、快捷栏),不阻挡玩家操作。使用 CreateUI 创建,需设置 isHud=1。
import mod.client.extraClientApi as clientApi
# 必须在 UiInitFinished 事件之后调用
def OnUIInitFinished(self, args):
clientApi.CreateUI(
"my_mod", # MOD 的 namespace
"my_hud", # UI 标识名
{"isHud": 1} # isHud=1 表示创建 HUD
)CreateUI 创建的界面默认不拦截输入,玩家可以正常移动和操作。
重要
CreateUI 和 PushScreen 必须在 UiInitFinished 事件触发之后调用,否则界面无法正常创建。RegisterUI 则可以在此事件之前调用。
创建交互界面
交互界面(如背包、商店)会阻挡玩家操作,使用 PushScreen 创建。界面以栈的形式管理。
import mod.client.extraClientApi as clientApi
def OpenShop(self):
clientApi.PushScreen(
"my_mod", # MOD 的 namespace
"shop_screen" # UI 标识名
)Screen 属性
PushScreen 创建的界面支持以下 screen 级属性(仅对 PushScreen 生效):
| 属性 | 默认值 | 说明 |
|---|---|---|
render_game_behind | true | 是否在界面背后继续渲染游戏画面 |
always_accepts_input | false | 不在栈顶时是否仍接受输入 |
force_render_below | false | 是否渲染被本界面覆盖的下层界面 |
render_only_when_topmost | true | 是否仅在栈顶时才渲染 |
is_modal | true | 是否为模态界面(弹出后才释放下方界面) |
should_steal_mouse | false | 是否隐藏鼠标光标 |
关闭界面
| 方式 | 说明 |
|---|---|
clientApi.PopScreen() | 弹出栈顶界面(无参数) |
获取实例后 SetRemove | 更安全的关闭方式 |
PopScreen 陷阱
PopScreen() 始终弹出栈顶界面。如果在你的界面之上又有其他界面被 Push,PopScreen() 会弹错界面。更安全的做法是通过 GetUI 获取目标界面实例后调用 SetRemove。
Python 脚本绑定
注册 UI
使用 RegisterUI 将 JSON 界面与 Python 类绑定。screenDef 的格式为 namespace.screenName,其中 namespace 来自 JSON 文件中的 "namespace" 字段,screenName 为画布控件名(通常是 main)。
import mod.client.extraClientApi as clientApi
# RegisterUI 可以在 UiInitFinished 之前调用
clientApi.RegisterUI(
"my_mod", # MOD namespace
"my_hud", # UI 标识名
"myScripts.MyHudScreen", # Python 类的完整路径
"my_ui.main" # screenDef = JSON namespace + 画布名
)ScreenNode 基类
所有 UI 逻辑类都继承自 ScreenNode:
import mod.client.extraClientApi as clientApi
ScreenNode = clientApi.GetScreenNodeCls()
ViewBinder = clientApi.GetViewBinderCls()
class MyHudScreen(ScreenNode):
def __init__(self, namespace, name, param):
ScreenNode.__init__(self, namespace, name, param)
def Create(self):
"""界面创建时回调,在此初始化控件引用和事件监听"""
pass
def Destroy(self):
"""界面销毁时回调,在此清理资源"""
pass事件绑定
按钮点击等交互通过 ViewBinder 装饰器绑定,JSON 中的 button_mappings 或 $pressed_button_name 指定触发 ID:
JSON 侧:
"my_button@common.button": {
"$pressed_button_name": "#my_click",
"size": [100, 30]
}Python 侧:
class MyScreen(ScreenNode):
@ViewBinder.binding(ViewBinder.BF_ButtonClickUp, '#my_click')
def OnMyClick(self, args):
print("按钮被点击了")UiInitFinished 事件
引擎 UI 系统初始化完成后触发,是创建 UI 的安全时机:
def InitClient(self):
import mod.client.extraClientApi as clientApi
self.ListenForEngineEvent(
"UiInitFinishedEvent", self, self.OnUIInitFinished
)
def OnUIInitFinished(self, args):
clientApi.CreateUI("my_mod", "my_hud", {"isHud": 1})数据绑定
JSON UI 支持 bindings 属性实现数据驱动。引擎内置了一些全局绑定(如 #hud_visible),也可以在 Python 中自定义绑定来实现服务端到客户端的数据同步。
内置绑定示例
让 HUD 控件跟随原版 F1 隐藏和透明度设置:
"my_root_panel": {
"type": "panel",
"bindings": [
{
"binding_name": "#hud_visible",
"binding_name_override": "#visible",
"binding_type": "global"
},
{
"binding_name": "#hud_alpha",
"binding_name_override": "#alpha",
"binding_type": "global"
}
]
}自定义数据绑定
通过 ViewBinder 将 Python 数据推送到 JSON 控件:
class MyHudScreen(ScreenNode):
def __init__(self, namespace, name, param):
ScreenNode.__init__(self, namespace, name, param)
self.hp_text = "100/100"
@ViewBinder.binding(ViewBinder.BF_BindString, '#hp_display')
def GetHpText(self):
return self.hp_text对应 JSON 中设置 bindings:
"hp_label": {
"type": "label",
"text": "#hp_display",
"bindings": [
{
"binding_name": "#hp_display",
"binding_type": "global"
}
]
}服务端数据通过自定义事件发送到客户端 Mod,客户端收到后更新 ScreenNode 中的变量,绑定系统会自动刷新 UI 显示。
常见问题
PopScreen 弹错界面
PopScreen() 无参数时始终弹出栈顶界面。如果其他 Mod 或系统在你之后 Push 了新界面,你调用 PopScreen() 实际关闭的是别人的界面。
解决方案:通过 clientApi.GetUI("my_mod", "shop_screen") 获取界面实例,调用实例的 SetRemove() 方法精确关闭。
screenDef 格式错误
screenDef 必须为 namespace.screenName 格式。namespace 是 JSON 文件中 "namespace" 字段的值,screenName 是画布控件的名称(UI 编辑器默认为 main)。
错误示例:"my_ui" (缺少画布名)、"my_ui/main" (分隔符错误)
正确示例:"my_ui.main"
平台检测
中国版同时运行在 PC 和移动端,可通过 API 判断平台并显示对应界面:
import mod.client.extraClientApi as clientApi
platform = clientApi.GetPlatform()
# 0 = PC, 1 = iOS, 2 = Android也可以在 JSON 中使用 variables + $touch 全局变量来控制控件在不同平台的显隐:
"pc_only_panel": {
"type": "panel",
"visible": "$is_pc",
"$is_pc": true,
"variables": [
{
"$is_pc": false,
"requires": "$touch"
}
]
}图片不显示
- 确认图片格式为 PNG(不支持 JPG、BMP 等)
- 确认文件名仅包含字母、数字、下划线
- 确认
texture路径从textures/开始,不带文件后缀
控件被遮挡
- 检查控件树中的顺序,同级下方的控件渲染在上层
- 如需精确控制,使用
layer属性(值越大越靠前) - 确保父级 Panel 之间的顺序也正确
进阶主题
| 主题 | 说明 |
|---|---|
| 继承与自定义控件 | 创建可复用的控件模板,通过 @ 语法继承并重写属性 |
| 控件动画 | 偏移、透明度、裁剪、颜色等属性动画(anim_type) |
| 九宫格切图 | 使用 nineslice_size 实现图片缩放不变形 |
| modifications | 通过 modifications 属性修改原版或其他 Mod 的 UI |
| 按钮映射 | button_mappings 绑定键盘/手柄/触屏输入到自定义逻辑 |
| 界面适配 | 异形屏安全区(继承 common.base_screen)、多分辨率适配 |
