26 KiB
title, category, nav_order, tags, mentions
| title | category | nav_order | tags | mentions | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| JSON UI 入门指南 | 基础 | 1 |
|
|
JSON UI 入门指南
简介
:::warning JSON UI 已进入弃用阶段,推荐使用 Ore UI 替代。请注意,任何使用 JSON UI 的附加包将在未来几年内失效。 :::
:::tip 本文概述了 JSON UI 的基础知识。如需更详细的文档,请查阅 JSON UI 文档。 :::
游戏用户界面采用数据驱动模式,支持自定义修改。通过 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
{
"ui_defs": ["ui/button.json", "my_ui/main_menu.json"]
}
:::
- 必须包含从资源包根目录开始的完整文件路径(包括
.json扩展名) - 只需声明自建文件,无需包含原版或其他第三方文件
- 支持非
RP/ui/...路径的文件引用 - 可使用非
.json扩展名,但文件内容必须为合法 JSON
全局变量
在 _global_variables.json 中定义变量 "$info_text_color":
::: code-group
{
"$info_text_color": [0.8, 0.8, 0.8]
}
:::
其他 UI 文件可调用此变量:
::: code-group
{
"some_info": {
...
"text": "Hey",
"color": "$info_text_color"
}
}
{
"info": {
...
"text": "Information",
"color": "$info_text_color"
}
}
:::
- 支持定义多个变量(逗号分隔)
- 全局变量为单向常量,不可跨文件修改
命名空间
命名空间是 UI 文件的唯一标识符,用于跨文件引用元素。每个命名空间必须具有唯一名称。
示例命名空间 one 中的元素:
::: code-group
{
"namespace": "one",
"foobar": {...}
}
:::
在命名空间 two 中引用:
::: code-group
{
"namespace": "two",
"fizzbuzz@one.foobar": {...}
}
:::
跨命名空间引用格式:
"[元素名称]@[命名空间].[被引用元素]"
屏幕系统
屏幕文件包含游戏在特定场景调用的界面布局(如背包界面)。每个屏幕文件必须包含根元素供游戏直接访问。
屏幕文件具有数据访问隔离特性。
UI 元素
UI 元素是 JSON UI 的基本组成单元,每个命名空间内的元素名称必须唯一。
示例文本元素:
::: code-group
{
"test_element": {
"type": "label",
"text": "Hello World"
}
}
:::
元素类型
常用元素类型 (type 属性值):
label- 文本对象image- 图像渲染button- 交互按钮panel- 层叠容器stack_panel- 流式布局容器grid- 网格模板渲染factory- 动态元素生成器custom- 自定义渲染器screen- 根屏幕元素
动画系统
使用 anim_type 属性创建动画元素,可通过 anims 数组应用于其他元素。
示例动画元素:
::: code-group
{
"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
{
"test_element": {
// 定义变量
"$array_var": [10, 10],
"$str_var": "foobar",
// 使用变量
"size": "$array_var",
"text": "$str_var",
// 动态引用模板
"controls": [
{ "foobar@$str_var": {} }
]
}
}
:::
变量继承
支持通过元素继承覆盖变量:
::: code-group
{
"base_element": {
"$var1": 1,
"$var2": false
},
"derived_element@base_element": {
"$var1": 2 // 覆盖父元素变量
}
}
:::
当更改时,派生元素的任何属性都将被完全覆盖。
数据绑定
绑定机制用于将硬编码值与界面元素关联,并在处理元素时使用这些值。以下是一个使用硬编码文本的标签示例:
text属性值设定为#hardtext。通过bindings配置,我们可以获取硬编码变量#hardtext的值,使text属性能够正确调用。这种配置直接将#hardtext的值赋给text属性。
::: code-group
{
"label": {
"type": "label",
"text": "#hardtext",
"bindings": [
{
"binding_name": "#hardtext"
}
]
}
}
:::
另一种常见的配置形式如下:
::: code-group
{
"label": {
"type": "label",
"text": "#text",
"bindings": [
{
"binding_name": "#hardtext",
"binding_name_override": "#text"
}
]
}
}
:::
此时,#hardtext的值会被赋给#text绑定属性,进而传递给text属性。
这种机制在visible(可见性)和enabled(启用状态)属性中尤为常见。以下是一个组合示例:
::: code-group
{
"send_button": {
"bindings": [
{
"binding_name": "#using_touch",
"binding_name_override": "#visible"
}
]
},
"play_button": {
"bindings": [
{
"binding_name": "#play_button_enabled",
"binding_name_override": "#enabled"
}
]
}
}
:::
此处的#using_touch和#play_button_enabled存储布尔值。当使用触控设备时,#using_touch为true,否则为false。#play_button_enabled用于「添加外部服务器」界面,当所有文本字段(服务器名称、IP地址和端口号)均有内容时才会设为true。
因此,#using_touch的值会覆盖#visible绑定属性(该属性通常通过property_bag设置,等同于直接设置visible属性),同理#play_button_enabled会覆盖#enabled的值。
当需要根据开关状态显示特定面板时,需使用另一种绑定结构。这种配置需要指定数据源元素、源属性及目标属性:
::: code-group
{
"panel": {
...
"bindings": [
{
"binding_type": "view",
"source_control_name": "my_toggle", // 源元素名称
"source_property_name": "#toggle_state", // 需要获取的开关状态属性
"target_property_name": "#visible" // 待覆盖的目标属性
}
]
},
"my_toggle": {
...
}
}
:::
当开关被勾选时,#toggle_state会变为1或true,从而将元素的visible属性设为可见。取消勾选时,该值变为0或false,再次覆盖visible属性值。
条件性渲染
在标准属性体系下,通过屏幕显示状态控制基岩版UI系统具有挑战性。然而变量(variables)和绑定(bindings)在JSON UI中具有特殊地位,因为它们承载着来自基岩引擎的实时数据。通过巧妙的UI技巧组合,开发者可以完全控制UI元素的渲染条件。这些方法分为两大类:基于变量的条件渲染和基于绑定的条件渲染。
:::warning ⚠️ 注意
本示例适用于国际版的受限制的 JSON UI 系统(客户端无法实现脚本控制)
对中国版来说,不需要这种复杂的黑科技实现HUD元素的可见性控制。
但是也能因此学习到数据绑定的使用方法,何尝不是一种收获呢?
:::
变量条件渲染
变量可用于实现条件性UI渲染。UI变量是指前缀带$的特殊属性,例如hud_screen.json中的$actionbar_text就承载着引擎数据。观察hud_actionbar_text控件可知,该变量用于显示动作栏文本。
::: code-group
{
...
"hud_actionbar_text": {
"type": "image",
"size": [ "100%c + 12px", "100%c + 5px" ],
"offset": [ 0, "50%-68px" ],
"texture": "textures/ui/hud_tip_text_background",
"alpha": "@hud.anim_actionbar_text_background_alpha_out",
"controls": [
{
"actionbar_message": {
"type": "label",
"anchor_from": "center",
"anchor_to": "center",
"color": "$tool_tip_text",
"layer": 1,
"text": "$actionbar_text",
"localize": false,
"alpha": "@hud.anim_actionbar_text_alpha_out"
}
}
]
}
...
}
:::
通过visible属性可实现基于引擎变量的条件渲染。以下示例复制了$actionbar_text变量以便进行修改和比较(原始变量无法直接操作)。新建的$atext变量用于控制visible属性,其逻辑是"当动作栏文本不等于hello world时显示文本标签"。
::: code-group
{
...
"hud_actionbar_text": {
"type": "image",
"size": ["100%c + 12px", "100%c + 5px"],
"offset": [0, "50%-68px"],
"texture": "textures/ui/hud_tip_text_background",
"alpha": "@hud.anim_actionbar_text_background_alpha_out",
"controls": [
{
"actionbar_message": {
"type": "label",
"anchor_from": "center",
"anchor_to": "center",
"color": "$tool_tip_text",
"layer": 1,
"text": "$actionbar_text",
"localize": false,
"alpha": "@hud.anim_actionbar_text_alpha_out",
// 当动作栏文本等于"hello world"时忽略该文本标签
"$atext": "$actionbar_text",
"visible": "(非 ($atext = 'hello world'))"
}
}
]
}
...
}
:::
将此JSON转换为资源包使用的非侵入式UI文件应如下所示:
::: code-group
{
"hud_actionbar_text/actionbar_message": {
"$atext": "$actionbar_text",
"visible": "(非 ($atext = 'hello world'))"
}
}
:::
在启用资源包的世界中执行/title @s actionbar hello world时,动作栏将不会显示信息。其他动作栏指令仍可正常显示。若需要同时隐藏文本背景,可移除/actionbar_message节点。由于背景元素hud_actionbar_text被隐藏时,其子元素也会连带隐藏。
下面展示更复杂的变量条件渲染示例。此处需要使用动作栏工厂(actionbar factory)。工厂是元素生成器,其中hud_actionbar_text_factory等具有硬编码属性。该工厂在每次执行动作栏指令时重置其control_id内的元素,并传递$actionbar_text等特殊变量,这些数据只能通过工厂获取。
::: code-group
{
"black_conditional_image": {
"type": "image",
"texture": "textures/ui/Black",
"size": [16, 16],
"layer": 10,
"$atext": "$actionbar_text",
"visible": "($atext = 'hello world')"
},
"black_conditional_image_factory": {
"type": "panel",
"factory": {
"name": "hud_actionbar_text_factory",
"control_ids": {
"hud_actionbar_text": "black_conditional_image@hud.black_conditional_image"
}
}
},
"root_panel": {
"modifications": [
{
"array_name": "controls",
"operation": "insert_front",
"value": {
"black_conditional_image_factory@hud.black_conditional_image_factory": {}
}
}
]
}
}
:::
当动作栏文本等于hello world时,此示例会在HUD界面显示16x16黑色方块。开发者可为图像添加动画增强表现力。变量条件渲染不仅限于图像和文本,任何UI对象类型均可应用。结合动作栏文本的UI代码可实现高度定制化(至少在hud_screen.json中)。visible属性支持UI运算符,提供更精细的控制。任何承载引擎数据的变量都支持变量条件渲染。
绑定条件渲染
观察标题系统(title)时,可能误以为其使用变量系统。实际上标题系统采用绑定机制获取数据,如下所示:
::: code-group
{
...
"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": "(非 $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
{
...
"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": "(非 $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": "(非 (#text = 'hello world'))", // 检测标题文本是否不等于"hello world"
"target_property_name": "#visible" // 根据检测结果覆盖可见性属性
}
]
}
}
]
}
}
]
}
...
}
:::
转换为资源包文件时应如下配置:
::: code-group
{
"hud_title_text/title_frame/title": {
"modifications": [
{
"array_name": "bindings",
"operation": "insert_back",
"value": {
"binding_type": "view",
"source_property_name": "(非 (#text = 'hello world'))",
"target_property_name": "#visible"
}
}
]
}
}
:::
下方是更复杂的绑定条件渲染示例。当标题文本等于hello world时显示16x16黑色图像。虽然不强制要求使用标题工厂,但若涉及UI动画则推荐使用。
::: code-group
{
"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"
}
}
},
"root_panel": {
"modifications": [
{
"array_name": "controls",
"operation": "insert_front",
"value": {
"black_conditional_image_factory@hud.black_conditional_image_factory": {}
}
}
]
}
}
:::
字符串格式化
使用%.#s格式可以从字符串中截取指定长度的部分,其中#代表字符数量。示例:
{
"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允许重新定义控件输入与按钮行为的对应关系,支持键鼠、触屏和手柄输入。
按钮元素配置示例:
{
"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- 控件存在时全局生效
条件触发示例:
{
"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 |
移除目标元素 |
操作示例
首尾操作
// 在控件列表开头插入新元素
{
"array_name": "controls",
"operation": "insert_front",
"value": [{"foo@example.bar": {}}]
}
// 将现有元素移至末尾
{
"array_name": "controls",
"operation": "move_back",
"value": [{"foo@example.bar": {}}]
}
相对定位操作
// 在指定绑定后插入新绑定
{
"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"}
}
替换与删除
// 替换现有绑定
{
"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"}
}