Skip to content

界面与交互 ​

中国版《我的世界》提供了一套基于 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 中注册,否则引擎无法找到:

json
{
  "ui_defs": [
    "ui/my_ui.json",
    "ui/shop_screen.json"
  ]
}

命名空间 ​

每个 JSON 文件都有一个全局唯一的 namespace,用于跨文件引用控件。建议 namespace 与文件名保持一致:

json
{
  "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 值说明
画布 Screenscreen界面根节点,所有控件必须在画布内
面板 Panelpanel透明容器,用于分组与布局,无专属属性
图片 Imageimage显示贴图,支持九宫格、裁剪、旋转、平铺
文本 Labellabel显示文字,支持颜色、字号、对齐、行间距
按钮 Buttonbutton可点击交互,支持默认/按下/悬浮三态贴图
输入框 EditBoxedit_box文字输入框
滚动视图 ScrollViewscroll_view可滚动的内容容器
网格 Gridgrid网格布局容器,常用于背包、列表
进度条 ProgressBarprogress_bar进度显示,基于图片裁剪实现
开关 Toggletoggle勾选框/开关
滑条 Sliderslider数值滑动条

提示

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 数组嵌套子控件,形成树形结构。子控件的位置、尺寸百分比均相对于父控件计算。

json
"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。

python
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 创建。界面以栈的形式管理。

python
import mod.client.extraClientApi as clientApi

def OpenShop(self):
    clientApi.PushScreen(
        "my_mod",           # MOD 的 namespace
        "shop_screen"       # UI 标识名
    )

Screen 属性 ​

PushScreen 创建的界面支持以下 screen 级属性(仅对 PushScreen 生效):

属性默认值说明
render_game_behindtrue是否在界面背后继续渲染游戏画面
always_accepts_inputfalse不在栈顶时是否仍接受输入
force_render_belowfalse是否渲染被本界面覆盖的下层界面
render_only_when_topmosttrue是否仅在栈顶时才渲染
is_modaltrue是否为模态界面(弹出后才释放下方界面)
should_steal_mousefalse是否隐藏鼠标光标

关闭界面 ​

方式说明
clientApi.PopScreen()弹出栈顶界面(无参数)
获取实例后 SetRemove更安全的关闭方式

PopScreen 陷阱

PopScreen() 始终弹出栈顶界面。如果在你的界面之上又有其他界面被 Push,PopScreen() 会弹错界面。更安全的做法是通过 GetUI 获取目标界面实例后调用 SetRemove。

Python 脚本绑定 ​

注册 UI ​

使用 RegisterUI 将 JSON 界面与 Python 类绑定。screenDef 的格式为 namespace.screenName,其中 namespace 来自 JSON 文件中的 "namespace" 字段,screenName 为画布控件名(通常是 main)。

python
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:

python
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 侧:

json
"my_button@common.button": {
  "$pressed_button_name": "#my_click",
  "size": [100, 30]
}

Python 侧:

python
class MyScreen(ScreenNode):
    @ViewBinder.binding(ViewBinder.BF_ButtonClickUp, '#my_click')
    def OnMyClick(self, args):
        print("按钮被点击了")

UiInitFinished 事件 ​

引擎 UI 系统初始化完成后触发,是创建 UI 的安全时机:

python
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 隐藏和透明度设置:

json
"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 控件:

python
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:

json
"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 判断平台并显示对应界面:

python
import mod.client.extraClientApi as clientApi
platform = clientApi.GetPlatform()
# 0 = PC, 1 = iOS, 2 = Android

也可以在 JSON 中使用 variables + $touch 全局变量来控制控件在不同平台的显隐:

json
"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)、多分辨率适配

更多内容请参考 UI 开发实战教程 和 UI 脚本交互。