Files
netease-modsdk-wiki/docs/wiki/4-Json-UI/1-基础/json-ui-intro.md
2025-03-19 22:17:04 +08:00

837 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: JSON UI 入门指南
category: 通用
nav_order: 1
tags:
- guide
mentions:
- sermah
- KalmeMarq
- SirLich
- solvedDev
- Joelant05
- GTB3NW
- stirante
- MedicalJewel105
- r4isen1920
- shanewolf38
- LeGend077
- mark-wiemer
- TheItsNameless
- ThomasOrs
---
# JSON UI 入门指南
<!--@include: @/wiki/bedrock-wiki-mirror.md-->
## 简介
:::warning
JSON UI 已进入弃用阶段,推荐使用 [Ore UI](https://github.com/Mojang/ore-ui) 替代。请注意,任何使用 JSON UI 的附加包将在未来几年内失效。
:::
:::tip
本文概述了 JSON UI 的基础知识。如需更详细的文档,请查阅 [JSON UI 文档](/json-ui/json-ui-documentation)。
:::
游戏用户界面采用数据驱动模式,支持自定义修改。通过 JSON UI 系统,我们可以调整用户界面的渲染方式及部分交互行为。所有原版 UI 文件均存储在 `RP/ui/...` 文件夹中。
JSON UI 可能包含以下文件类型:
### 系统文件
这些是 JSON UI 的内置文件:
- `_global_variables.json` - 存储全局变量定义
- `_ui_defs.json` - 管理 UI 文件引用清单
### 屏幕文件
用于定义特定界面布局的文件:
- `hud_screen.json` - 显示包含快捷栏等游戏元素的 HUD 界面
- `inventory_screen.json` - 玩家背包界面
- 其他屏幕文件
### 模板文件
存储可复用 UI 组件的文件:
- `ui_common.json` - 包含通用组件(如设置界面的按钮模板)
- `ui_template_*.json` - 模块化组织的组件集合
## UI 定义文件
`_ui_defs.json` 通过数组形式引用所有 JSON UI 文件。
例如新增 `RP/ui/button.json``RP/my_ui/main_menu.json` 时,应如下配置:
::: code-group
```json [RP/ui/_ui_defs.json]
{
"ui_defs": ["ui/button.json", "my_ui/main_menu.json"]
}
```
:::
- 必须包含从资源包根目录开始的完整文件路径(包括 `.json` 扩展名)
- 只需声明自建文件,无需包含原版或其他第三方文件
- 支持非 `RP/ui/...` 路径的文件引用
- 可使用非 `.json` 扩展名,但文件内容必须为合法 JSON
## 全局变量
在 `_global_variables.json` 中定义变量 `"$info_text_color"`
::: code-group
```json [RP/ui/_global_variables.json]
{
"$info_text_color": [0.8, 0.8, 0.8]
}
```
:::
其他 UI 文件可调用此变量:
::: code-group
```json [vanilla/my_ui/file1.json]
{
"some_info": {
...
"text": "Hey",
"color": "$info_text_color"
}
}
```
```json [vanilla/my_ui/file2.json]
{
"info": {
...
"text": "Information",
"color": "$info_text_color"
}
}
```
:::
- 支持定义多个变量(逗号分隔)
- 全局变量为单向常量,不可跨文件修改
## 命名空间
命名空间是 UI 文件的唯一标识符,用于跨文件引用元素。每个命名空间必须具有唯一名称。
示例命名空间 `one` 中的元素:
::: code-group
```json [vanilla/ui/file_a.json]
{
"namespace": "one",
"foobar": {...}
}
```
:::
在命名空间 `two` 中引用:
::: code-group
```json [vanilla/ui/file_b.json]
{
"namespace": "two",
"fizzbuzz@one.foobar": {...}
}
```
:::
跨命名空间引用格式:
```json
"[元素名称]@[命名空间].[被引用元素]"
```
## 屏幕系统
屏幕文件包含游戏在特定场景调用的界面布局(如背包界面)。每个屏幕文件必须包含根元素供游戏直接访问。
屏幕文件具有数据访问隔离特性。
## UI 元素
UI 元素是 JSON UI 的基本组成单元,每个命名空间内的元素名称必须唯一。
示例文本元素:
::: code-group
```json [vanilla/ui/example_file.json]
{
"test_element": {
"type": "label",
"text": "Hello World"
}
}
```
:::
### 元素类型
常用元素类型 (`type` 属性值)
- `label` - 文本对象
- `image` - 图像渲染
- `button` - 交互按钮
- `panel` - 层叠容器
- `stack_panel` - 流式布局容器
- `grid` - 网格模板渲染
- `factory` - 动态元素生成器
- `custom` - 自定义渲染器
- `screen` - 根屏幕元素
## 动画系统
使用 `anim_type` 属性创建动画元素,可通过 `anims` 数组应用于其他元素。
示例动画元素:
::: code-group
```json [vanilla/ui/example_file.json]
{
"namespace": "example_nm",
"anim_size": {
"anim_type": "size",
"easing": "linear",
"from": [ "100%", 27 ],
"to": [ "100% + 3px", 30 ],
"duration": 1.25
},
"anim_alpha": {
"anim_type": "alpha",
"easing": "linear",
"from": 1,
"to": 0.5,
"duration": 2
},
"test_animated_element": {
...
"anims": [
"@example_nm.anim_size",
"@example_nm.anim_alpha"
]
}
}
```
:::
### 动画类型
支持动画类型 (`anim_type`)
- `alpha` - 透明度动画
- `offset` - 位移动画
- `size` - 尺寸动画
- `flip_book` - 翻页动画
- `uv` - UV 贴图动画
- `color` - 颜色过渡
- `wait` - 等待延时
- `aseprite_flip_book` - 精灵表动画
- `clip` - 裁剪动画
## 操作符系统
支持在属性中使用运算符,结合 `$变量` 和 `#绑定` 实现动态计算。
| 运算符 | 符号 | 示例 |
|----------------------|------|----------------------------------------------------------------------|
| 加法 | + | `"100% + 420px"` `($text + ' my')` |
| 减法 | - | `"100% - 69px"` `($index - 13)` |
| 乘法 | * | `($var * 9)` |
| 除法 | / | `(#value / 2)` |
| 等于 | = | `($var = 'text')` |
| 大于 | > | `(#value > 13)` |
| 小于 | < | `($var < 4)` |
| 大于等于 | >= | `(#value >= 2)` |
| 小于等于 | <= | `(#value <= 2)` |
| 逻辑与 | and | `($cond1 and $cond2)` |
| 逻辑或 | or | `($condA or $condB)` |
| 逻辑非 | not | `(not #flag)` |
## 变量系统
除全局变量外,支持在元素内定义局部变量。
### 变量定义
使用 `$` 前缀定义变量,支持多种数据类型:
::: code-group
```json [vanilla/ui/example_file.json]
{
"test_element": {
// 定义变量
"$array_var": [10, 10],
"$str_var": "foobar",
// 使用变量
"size": "$array_var",
"text": "$str_var",
// 动态引用模板
"controls": [
{ "foobar@$str_var": {} }
]
}
}
```
:::
### 变量继承
支持通过元素继承覆盖变量:
::: code-group
```json [vanilla/ui/example_file.json]
{
"base_element": {
"$var1": 1,
"$var2": false
},
"derived_element@base_element": {
"$var1": 2 // 覆盖父元素变量
}
}
```
:::
## 数据绑定
通过 `bindings` 实现数据源与元素的动态关联。
### 简单绑定
::: code-group
```json [vanilla/ui/example_file.json]
{
"label": {
"type": "label",
"text": "#hardtext",
"bindings": [
{
"binding_name": "#hardtext"
}
]
}
}
```
:::
### 重定向绑定
::: code-group
```json [vanilla/ui/example_file.json]
{
"label": {
"type": "label",
"text": "#display_text",
"bindings": [
{
"binding_name": "#source_data",
"binding_name_override": "#display_text"
}
]
}
}
```
:::
### 跨元素绑定
::: code-group
```json
{
"status_panel": {
"bindings": [
{
"binding_type": "view",
"source_control_name": "my_toggle",
"source_property_name": "#state",
"target_property_name": "#visible"
}
]
}
}
```
:::
## 条件渲染
通过变量和绑定实现动态显示控制。
### 变量条件
::: code-group
```json [vanilla/ui/hud_screen.json]
{
"hud_actionbar_text/actionbar_message": {
"$atext": "$actionbar_text",
"visible": "(not ($atext = 'hello world'))"
}
}
```
:::
### 工厂条件
::: code-group
```json [vanilla/ui/hud_screen.json]
{
"conditional_image": {
"type": "image",
"texture": "textures/ui/Black",
"$atext": "$actionbar_text",
"visible": "($atext = 'show_image')"
},
"image_factory": {
"type": "panel",
"factory": {
"name": "hud_actionbar_text_factory",
"control_ids": {
"hud_actionbar_text": "conditional_image@hud.conditional_image"
}
}
}
}
```
:::
通过结合操作符系统,可实现复杂的条件逻辑判断,为界面交互提供灵活的控制能力。
::: code-group
```json [vanilla/ui/hud_screen.json]
```
:::
### 使用绑定的条件渲染
根据上文提到的操作栏示例你可能会认为标题也使用变量。但实际情况并非如此。标题使用绑定bindings来获取数据如下所示。
::: code-group
```json [vanilla/ui/hud_screen.json]
{
...
"hud_title_text": {
"type": "stack_panel",
"orientation": "vertical", // 垂直排列
"offset": [ 0, -19 ], // 位置偏移
"layer": 1, // 渲染层级
"alpha": "@hud.anim_title_text_alpha_in", // 透明度动画
"propagate_alpha": true, // 透明度继承
"controls": [ // 子控件集合
{
"title_frame": {
"type": "panel", // 面板类型
"size": [ "100%", "100%cm" ], // 尺寸设置
"controls": [
{
"title_background": {
"type": "image", // 图像类型
"size": [ "100%sm + 30px", "100%sm + 6px" ], // 动态尺寸计算
"texture": "textures/ui/hud_tip_text_background", // 纹理路径
"alpha": "@hud.anim_title_background_alpha_in" // 背景透明度动画
}
},
{
"title": {
"type": "label", // 文本标签类型
"anchor_from": "top_middle", // 锚点起始位置
"anchor_to": "top_middle", // 锚点目标位置
"color": "$title_command_text_color", // 文字颜色变量
"text": "#text", // 文本内容绑定
"layer": 1, // 渲染层级
"localize": false, // 关闭本地化
"font_size": "extra_large", // 超大字号
"variables": [ // 条件变量组
{
"requires": "(not $title_shadow)", // 无阴影条件
"$show_shadow": false // 关闭阴影显示
},
{
"requires": "$title_shadow", // 启用阴影条件
"$show_shadow": true // 启用阴影显示
}
],
"shadow": "$show_shadow", // 阴影状态绑定
"text_alignment": "center", // 文本居中
"offset": [ 0, 6 ], // 位置微调
"bindings": [ // 数据绑定组
{
"binding_name": "#hud_title_text_string", // 原始绑定名
"binding_name_override": "#text", // 覆盖目标属性
"binding_type": "global" // 全局绑定类型
}
]
}
}
]
}
}
]
}
...
}
```
:::
我们需要在文本组件中添加另一个绑定对象来控制可见性。注意`#visible`属性会通过绑定直接控制元素可见性。以下示例将不会渲染"hello world"标题,但会显示其他所有标题。可在游戏中输入`/title @s title hello world`观察效果。
::: code-group
```json [vanilla/ui/hud_screen.json]
{
...
"hud_title_text": {
"type": "stack_panel",
"orientation": "vertical",
"offset": [ 0, -19 ],
"layer": 1,
"alpha": "@hud.anim_title_text_alpha_in",
"propagate_alpha": true,
"controls": [
{
"title_frame": {
"type": "panel",
"size": [ "100%", "100%cm" ],
"controls": [
{
"title_background": {
"type": "image",
"size": [ "100%sm + 30px", "100%sm + 6px" ],
"texture": "textures/ui/hud_tip_text_background",
"alpha": "@hud.anim_title_background_alpha_in"
}
},
{
"title": {
"type": "label",
"anchor_from": "top_middle",
"anchor_to": "top_middle",
"color": "$title_command_text_color",
"text": "#text",
"layer": 1,
"localize": false,
"font_size": "extra_large",
"variables": [
{
"requires": "(not $title_shadow)",
"$show_shadow": false
},
{
"requires": "$title_shadow",
"$show_shadow": true
}
],
"shadow": "$show_shadow",
"text_alignment": "center",
"offset": [ 0, 6 ],
"bindings": [
{
"binding_name": "#hud_title_text_string",
"binding_name_override": "#text",
"binding_type": "global"
},
{
"binding_type": "view", // 将此设为视图绑定
"source_property_name": "(not (#text = 'hello world'))", // 当标题文本不等于"hello world"时触发
"target_property_name": "#visible" // 根据条件覆盖可见性属性
}
]
}
}
]
}
}
]
}
...
}
```
:::
在资源包中使用非侵入式UI文件修改时应保持如下格式
::: code-group
```json [RP/ui/hud_screen.json]
{
"hud_title_text/title_frame/title": {
"modifications": [
{
"array_name": "bindings", // 目标数组名
"operation": "insert_back", // 末尾插入操作
"value": { // 新增绑定对象
"binding_type": "view",
"source_property_name": "(not (#text = 'hello world'))",
"target_property_name": "#visible"
}
}
]
}
}
```
:::
下面是一个更复杂的条件渲染示例。16x16的黑色图片仅在标题文本等于"hello world"时显示。虽然在此案例中不需要使用标题工厂title factory但如需使用UI动画则建议采用。
::: code-group
```json [RP/ui/hud_screen.json]
{
"black_conditional_image": {
"type": "image",
"texture": "textures/ui/Black", // 黑色纹理
"size": [16, 16], // 固定尺寸
"layer": 10, // 较高渲染层级
"bindings": [
{
"binding_name": "#hud_title_text_string" // 标题文本绑定
},
{
"binding_type": "view",
"source_property_name": "(#hud_title_text_string = 'hello world')", // 条件判断
"target_property_name": "#visible" // 可见性控制
}
]
},
"black_conditional_image_factory": {
"type": "panel",
"factory": {
"name": "hud_title_text_factory", // 使用标题工厂
"control_ids": {
"hud_title_text": "black_conditional_image@hud.black_conditional_image" // 控件ID映射
}
}
},
"root_panel": {
"modifications": [
{
"array_name": "controls", // 根面板控件数组
"operation": "insert_front", // 前置插入
"value": {
"black_conditional_image_factory@hud.black_conditional_image_factory": {} // 工厂实例
}
}
]
}
}
```
:::
## 字符串格式化
使用`%.#s`格式可以从字符串中截取指定长度的部分,其中`#`代表字符数量。示例:
```json
{
"label_element": {
"type": "label",
"text": "#text", // 文本绑定
"layer": 2,
"bindings": [
{
"binding_type": "global",
"binding_name": "#hud_title_text_string" // 全局标题绑定
},
{
"binding_type": "view",
"source_property_name": "('%.3s' * #hud_title_text_string)", // 截取前3个字符
"target_property_name": "#text" // 输出结果
}
]
}
}
```
假设变量`"$var": "abcdefghijklmn"`,则:
- `'%.5s' * $var` 返回 `abcde`
- `$var - ('%.7s' * $var)` 返回 `hijklm`
注意该格式的使用场景较为有限。
## 按钮映射
`button_mappings`允许重新定义控件输入与按钮行为的对应关系,支持键鼠、触屏和手柄输入。
按钮元素配置示例:
```json
{
"sample_button@common.button": {
"$pressed_button_name": "button_id", // 按钮ID变量
"button_mappings": [
{
"to_button_id": "$pressed_button_name",
"mapping_type": "pressed" // 按压映射
},
{
"from_button_id": "button.menu_ok", // 来源按钮
"to_button_id": "$pressed_button_name", // 目标按钮
"mapping_type": "focused" // 焦点状态映射
},
{
"from_button_id": "button.menu_select", // 选择按钮
"to_button_id": "$pressed_button_name",
"mapping_type": "pressed"
},
{
"from_button_id": "button.menu_up", // 上方向键
"to_button_id": "$pressed_button_name",
"mapping_type": "global" // 全局映射
}
]
}
}
```
### 映射类型
定义按钮映射的作用范围:
- `focused` - 控件获得焦点时生效
- `pressed` - 控件被点击/按压时生效
- `global` - 控件存在时全局生效
条件触发示例:
```json
{
"sample_button@common.button": {
"$pressed_button_name": "button_id",
"button_mappings": [
// 鼠标悬停时触发
{
"from_button_id": "button.menu_ok",
"to_button_id": "$pressed_button_name",
"mapping_type": "focused"
},
// 点击时触发
{
"from_button_id": "button.menu_select",
"to_button_id": "$pressed_button_name",
"mapping_type": "pressed"
},
// 全局响应上方向键
{
"from_button_id": "button.menu_up",
"to_button_id": "$pressed_button_name",
"mapping_type": "global"
}
]
}
}
```
### 常用按钮ID
**键鼠映射表:**
| 按钮ID | 说明 |
|-----------------------------|---------------|
| `button.menu_select` | 鼠标左键 |
| `button.menu_secondary_select` | 鼠标右键 |
| `button.menu_ok` | 回车键 |
| `button.menu_exit` | ESC键 |
| `button.menu_cancel` | ESC键 |
| `button.menu_up` | 上方向键 |
| `button.menu_down` | 下方向键 |
| `button.menu_left` | 左方向键 |
| `button.menu_right` | 右方向键 |
| `button.menu_autocomplete` | Tab键 |
**手柄映射表:**
| 按钮ID | 说明 |
|-----------------------------|-------------|
| `button.controller_select` | X/A键 |
| `button.menu_secondary_select` | Y键 |
| `button.menu_exit` | B键 |
| `button.menu_cancel` | B键 |
| `button.menu_up` | 方向键上 |
| `button.menu_down` | 方向键下 |
| `button.menu_left` | 方向键左 |
| `button.menu_right` | 方向键右 |
建议在设计UI时兼容多种输入设备。
## 修改操作
使用`modifications`属性可以非侵入式地修改现有JSON UI元素提升资源包兼容性。
| 操作类型 | 描述 |
|------------------|-----------------------|
| `insert_back` | 在数组末尾插入 |
| `insert_front` | 在数组开头插入 |
| `insert_after` | 在目标元素后插入 |
| `insert_before` | 在目标元素前插入 |
| `move_back` | 移动元素到数组末尾 |
| `move_front` | 移动元素到数组开头 |
| `move_after` | 移动元素到目标之后 |
| `move_before` | 移动元素到目标之前 |
| `swap` | 交换两个元素位置 |
| `replace` | 替换目标元素 |
| `remove` | 移除目标元素 |
### 操作示例
#### 首尾操作
```json
// 在控件列表开头插入新元素
{
"array_name": "controls",
"operation": "insert_front",
"value": [{"foo@example.bar": {}}]
}
// 将现有元素移至末尾
{
"array_name": "controls",
"operation": "move_back",
"value": [{"foo@example.bar": {}}]
}
```
#### 相对定位操作
```json
// 在指定绑定后插入新绑定
{
"array_name": "bindings",
"operation": "insert_after",
"where": {"binding_name": "#example_binding_2"},
"value": [{"binding_name": "#my_binding_1"}]
}
// 移动绑定到指定位置前
{
"array_name": "bindings",
"operation": "move_before",
"where": {"binding_name": "#example_binding_1"},
"target": {"binding_name": "#example_binding_2"}
}
```
#### 替换与删除
```json
// 替换现有绑定
{
"array_name": "bindings",
"operation": "replace",
"where": {"binding_name": "#example_binding_1"},
"value": {"binding_name": "#replacement_binding"}
}
// 删除指定绑定
{
"array_name": "bindings",
"operation": "remove",
"where": {"binding_name": "#example_binding_1"}
}
```