同步官网文档8m_25d

This commit is contained in:
kwiilh
2025-08-25 18:36:29 +08:00
parent 4dc0ecf18d
commit 9e8855eeb4
5089 changed files with 8798 additions and 4799 deletions

View File

@@ -1,34 +1,367 @@
---
front:
hard: 入门
time: 分钟
front:
hard: 困难
time: 120分钟
---
# UI数据绑定
## 简介
当我们想要改变UI控件的显示例如修改label控件显示的文字通常有两种方法一种是调用UI API修改一种就是使用绑定。绑定就是将Python变量的值与UI控件的属性关联到一起。当我们修改Python变量的数据后UI的显示会自动刷新不用再手动调用API去设置控件属性从而实现了数据和渲染的统一。
UI API和绑定各有优劣UI API更容易理解代码也更简单。而绑定尤其是集合绑定拥有更好的性能例如无尽贪婪的9×9工作台界面若使用集合绑定可以实现第一次秒开。而使用API去一个一个设置格子的话往往要卡1秒左右才能打开。
## 绑定流程
例如现在我们想绑定label的text属性首先要定义一个bindings属性格式为array(object)或object用于定义绑定规则。
接着在其中新增一个绑定对象,定义绑定变量为`#abcdefg`,绑定条件为`always_when_visible`可见时绑定然后把它指定到text属性上。
```json
"label0": {
"bindings": [{
"binding_name": "#abcdefg", // 引擎会去找Python里的#abcdefg
"binding_condition": "always_when_visible"
}],
"text": "#abcdefg" // 把#abcdefg的值指定到text属性上
...
},
```
绑定变量必须以`#`开头,不要和`$`属性变量相混淆,`$`变量是纯JSON层面的变量例如你完全可以这样写
```json
"label0": {
"$my_binding_name": "#abcdefg"
"bindings": [{
"binding_name": "$my_binding_name",
...
}],
...
},
```
而binding_name会对应到UI类中带有ViewBinder装饰器的Python函数游戏会以渲染帧高频调用该函数开发者在该函数中返回对应的Python变量即可。
**binding(bind_flag, binding_name = None)**
```python
@ViewBinder.binding(ViewBinder.BF_BindString, '#abcdefg')
def ReturnAbcdefg(self):
return self.oneText # 当self.oneText改变#abcdefg就会改变label0控件就会自动刷新
```
BF_BindString用于声明绑定的数据类型是str`#abcdefg`对应JSON里的binding_name。
Python侧支持的绑定分为binding单个绑定和binding_collection集合绑定已支持绑定的数据类型如下
| 绑定类型 | 绑定方式 | 解释 |
| :--------------------- | :---------------------------: | :------------------------------------- |
| BF_ButtonClickUp | binding | 绑定按钮的Up事件 |
| BF_ButtonClickDown | binding | 绑定按钮的Down事件 |
| BF_ButtonClick | binding | 同时绑定Up和Down事件 |
| BF_ButtonClickCancel | binding | 绑定按钮的Cancel事件按钮down其他up |
| BF_InteractButtonClick | binding | 绑定游戏原生的按钮点击事件 |
| BF_BindBool | binding \| binding_collection | 绑定Bool变量 |
| BF_BindInt | binding \| binding_collection | 绑定Int变量 |
| BF_BindFloat | binding \| binding_collection | 绑定Float变量 |
| BF_BindString | binding \| binding_collection | 绑定String变量 |
| BF_BindGridSize | binding | 绑定GridSize变量 |
| BF_BindColor | binding \| binding_collection | 绑定颜色变量 |
| BF_EditChanged | binding | 绑定输入框输入改变事件 |
| BF_EditFinished | binding | 绑定输入框输入完成事件 |
| BF_ToggleChanged | binding | 开关状态改变事件 |
```python
# 绑定类型的枚举
class ViewBinder(object):
ButtonFilter = 0x10000000
BF_ButtonClickUp = 0 | ButtonFilter
BF_ButtonClickDown = 1 | ButtonFilter
BF_ButtonClick = 2 | ButtonFilter
BF_ButtonClickCancel= 3
BF_InteractButtonClick = 4
BindFilter = 0x01000000
BF_BindBool = 5 | BindFilter
BF_BindInt = 6 | BindFilter
BF_BindFloat = 7 | BindFilter
BF_BindString = 8 | BindFilter
BF_BindGridSize = 9 | BindFilter
BF_BindColor = 10 | BindFilter
EditFilter = 0x00100000
BF_EditChanged = 11 | EditFilter
BF_EditFinished = 12 | EditFilter
ToggleFilter = 0x00010000
BF_ToggleChanged = 13 | ToggleFilter
# 返回值类型的枚举
class ViewRequest(object):
Nothing = 0
Refresh = 1 << 0
PointerHeldEventsRequest = 1 << 1
PointerHeldEventsCancel = 1 << 2
Exit = 1 << 3
```
## 绑定对象
在继续深入学习绑定前我们先了解bindings中的绑定对象可具有的属性。
| 名称 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| ignored | boolean | false | 是否忽略该绑定 |
| binding_type | enum | global | 绑定类型,可选的值:<br>- global全局绑定<br>- view视图绑定<br>- collection集合绑定<br>- collection_details集合详情绑定<br>- none |
| binding_condition | enum | none | 数据绑定的条件。可选的值:<br>- always持续触发<br>- always_when_visible在可见时持续触发<br>- visible当可见时触发一次<br>- visibility_changed当可见性发生改变时触发<br>- once单次触发<br>- none |
当binding_type为`global`(全局绑定)时额外支持以下属性
| 名称 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| binding_name | string | | 在引擎代码或Python脚本中用于对接的绑定名称需要自己起名支持字符串表达式 |
| binding_name_override | string | "" | 要映射到的的控件属性名称,具体见下文 |
当binding_type为`collection`(集合绑定)时额外支持以下属性
| 名称 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| binding_name | string | | 在引擎代码或Python脚本中用于对接的绑定名称需要自己起名支持字符串表达式 |
| binding_name_override | string | "" | 要映射到的的控件属性名称,具体见下文 |
| binding_collection_name | string | | 在引擎或Python脚本中用于对接的集合绑定名称需要自己起名支持字符串表达式 |
当binding_type为`collection_details`(集合详情绑定)时额外支持以下属性
| 名称 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| binding_name | string | | 在引擎代码或Python脚本中用于对接的绑定名称需要自己起名支持字符串表达式 |
| binding_name_override | string | "" | 要映射到的的控件属性名称,具体见下文 |
| binding_collection_name | string | | 在引擎或Python脚本中用于对接的集合绑定名称需要自己起名支持字符串表达式 |
| binding_collection_prefix | string | "" | 集合绑定的前缀 |
当binding_type为`view`(视图绑定)时额外支持以下属性
| 名称 | 类型 | 默认值 | 描述 |
| --- | --- | --- | --- |
| source_control_name | string | "" | 要查看的源控件的控件名,默认为当前控件自身 |
| source_property_name | string | | 要查看的源控件的绑定属性名,支持字符串表达式 |
| target_property_name | string | | 要将查看得到的计算值赋给的绑定属性名 |
| resolve_sibling_scope | boolean | false | 是否解析兄弟控件的作用域如果为true将在同controls中的兄弟控件/父控件中而不是在子控件中寻找source_control_name |
## 绑定的默认值
property_bag用于定义绑定变量的默认值假如Python没有返回数据那么变量会使用默认值。
```
"label0": {
"text": "#abcdefg",
"property_bag": {
"#abcdefg": "喜欢小星星"
},
...
},
```
这样写完后例如Python没有返回数据text会显示默认值`喜欢小星星`
## 绑定的映射
当我们在 Python 端编写了一个绑定函数,并且在 JSON 端编写了控件及其“绑定变量”时,如果二者之间的“名称”不一致,就可以在 JSON 里做一个“名字映射”,以让引擎知道“控件最终要拿哪个 Python 返回的值”来更新自身的属性值。而这个“名字映射”就由 binding_name 和 binding_name_override 共同完成。
如果直接将 binding_name 与控件需要的属性同名那么“控件需要的属性名”和“Python 函数里声明的绑定变量名”就会一致,进而“一一对应”,不需要任何额外映射。但在实际项目中,我们可能会遇到以下需求或问题:
1. 你的控件属性名是 #abcdefg,但 Python 中的绑定名偏偏叫 #my_python_binding,二者不一样。
2. 界面上不止一个属性值需要使用同一个绑定变量。举例:我们想让“某个整数”既用来显示文本,又控制可见性(具体见视图绑定部分)。
3. 你不想改 Python 里的函数名,也不想改 JSON 里已有的属性名(因为牵涉到其他位置的调用)。
这些场景下binding_name_override 就能发挥作用:
• binding_name用来告诉引擎“我要引用 Python 端哪个绑定变量”。
• binding_name_override用来告诉引擎“我把这个绑定变量的值最终要赋给 UI 的哪个属性”。
假设你原本写了一个 Python 绑定函数,返回一个字符串,名为:
```python
@ViewBinder.binding(ViewBinder.BF_BindString, '#my_python_binding')
def ReturnMyStr(self):
return self.someText
```
而在某个 JSON 中,你的控件 label 想直接显示这个字符串。那么最直接的做法就是:
```json
"label0": {
"text": "#my_python_binding",
"bindings": [{
"binding_name": "#my_python_binding",
"binding_condition": "always_when_visible"
}]
}
```
这样就能让“label0 的 text 属性”直接绑定到“Python 的 #my_python_binding”。这里没有用到 binding_name_override因为 JSON 的 binding_name 和 label 的 text 属性使用的都是 #my_python_binding,一致即可。
假如在某个 JSON 中label 的 text 写了 #abcdefg,但是 Python 端的函数叫 #my_python_binding,你又不打算重命名两端已有的名字,那么可以这样写:
```json
"label0": {
"text": "#abcdefg", // 这里的#abcdefg需要手动指定给text属性
"bindings": [{
"binding_name": "#my_python_binding", // 引擎会去找 Python 里的 #my_python_binding
"binding_name_override": "#abcdefg", // 把该值映射给本控件的 #abcdefg
"binding_condition": "always_when_visible"
}]
}
```
这里的#abcdefg需要手动指定给text属性但是对于一些基础变量如alpha、visible以及渲染器专用绑定字段#item_id_aux#item_custom_color等还有之前在UI说明文档中启用use_anchored_offset后新增的一些绑定变量并不需要手动指定给对应的属性可以直接绑定。
```json
"label0": {
"bindings": [{
"binding_name": "#xxxxx_visible", //对应的Python绑定名称
"binding_name_override": "#visible", //直接映射给visible属性
"binding_condition": "always"
}],
...
},
```
如上方代码所示如需使用绑定控制label0的显示和隐藏直接将Python侧的某个变量#xxxxx_visible映射给#visible即可,并不需要定义`"visible": "#visible"`这里我们使用binding_name_override。当然你也可以像之前绑定text一样将一个专门的绑定变量指定给visible反正都是多写一行。
有了 binding_name_override你就可以让“多端命名不统一”在 JSON 层面灵活处理,而不必修改所有 Python 文件或所有 UI JSON 文件。
## 集合绑定
假如我们现在有一个grid用于背包里面有36个格子每个格子都可能显示不同的物品。如果使用单个绑定来控制里面的物品渲染那就得为每个控件编写对应的绑定变量和回调函数36个格子就得写36个函数这无疑是极其低效的。
所以mojang开发了集合绑定专门用来解决grid的绑定问题。和单个绑定不同的是我们只需要定义一个绑定变量和一个回调函数即可控制格子里的所有物品。之后网易又开发了stack_grid控件其实就是一维的grid也可以使用集合绑定。而剩余的其他控件不能使用也无需使用集合绑定。
集合绑定的原理是通过在绑定时新增一个index参数之后引擎在调用绑定方法时会把当前要渲染的index传给开发者开发者根据不同的index给引擎返回不同的数据。引擎在每个渲染帧会将grid的所有index从小到大都调用一遍比如有36个格子每帧就会调用36遍index从0到35这样利用一个index避免了写36次绑定。
**binding_collection(bind_flag, collection_name, binding_name = None)**
```python
# 演示了从物品信息字典列表里取出count并转为str返回给引擎用于显示物品格子右下角数字
@ViewBinder.binding_collection(ViewBinder.BF_BindString, "abcdefg_item", "#abcdefg")
def ReturnItemCount(self, index):
return str(self.itemDictList[index]['count'])
```
使用集合绑定首先要给grid定义一个集合名即collection_name如abcdefg_item。
```
"inventory_grid": {
"type": "grid",
"collection_name": "abcdefg_item",
"grid_dimensions": [9, 3],
"grid_item_template": "xxxxxxx.xxxx"
}
```
然后在grid_item_template对应的控件里进行集合绑定比如现在我们有一个物品格子里面包含一个右下角的文本控件用于展示物品数量可以这样写
```
"controls": [{
"label0": {
"anchor_from": "bottom_right",
"anchor_to": "bottom_right",
"text": "#abcdefg",
"bindings": [{
"binding_type": "collection",
"binding_collection_name": "abcdefg_item",
"binding_name": "#abcdefg",
"binding_condition": "always_when_visible"
}],
...
},
...
}]
```
这样就对应了上方的Python代码。以上仅作示例实际开发建议直接继承common.container_item即可。至于物品格子中的其他控件如耐久条、物品渲染、在编写时binding_collection_name都是一致的这样index就都是一致的。而binding_name则会用于控制不同的属性所以会不一样。
## 视图绑定
视图绑定用于绑定变量的灵活复用,因为无论是全局绑定还是集合绑定,最终绑定到的变量,只有控件本身可以读取。而视图绑定可以读取本控件或其他控件中的变量,并在计算后设置到本控件的变量中,使得绑定能够更加灵活。
编写视图绑定时首先要确定想获取哪个控件的变量然后把它的路径填在source_control_name里不填则默认为视图绑定所在的控件自身。然后把需要读取的变量名称填在source_property_name里支持表达式然后要把要覆盖值的变量写在target_property_name里。这里和binding_name_override的区别在于一旦定义了binding_name_override重载了binding_name那么控件本身binding_name对应的变量会消失不能被视图绑定找到。但source_control_name对应到target_property_name并不会导致source_control_name的变量被清除。
例如耐久条渲染器参考common.durability_bar我们省略其他代码这里利用视图绑定实现了当物品不存在耐久值概念时#progress_bar_current_amount < 1.0就隐藏耐久条的效果#progress_bar_total_amount永远设为1.0Python只传剩余耐久比例给#progress_bar_current_amount这样就能避免Python判断节约了性能
```json
"durability_bar": {
"type": "custom",
"renderer": "progress_bar_renderer",
"property_bag": {
"#progress_bar_total_amount": 1.0,
...
},
"bindings": [
{
"binding_type": "view",
"source_property_name": "(#progress_bar_current_amount < 1.0)", //需要加括号
"target_property_name": "#touch_progress_bar_visible"
}
...
]
}
```
如果定义了source_control_name当resolve_sibling_scope为false默认只能选择绑定所在的本控件或controls里的子控件如果改成true就能在同controls中的兄弟控件和父控件中查找source_control_name了
例如如下代码实现了利用自定义变量#lock_mode的值动态显示物品槽位右上角的黄色和红色角标表示是否为[锁定物品](https://zh.minecraft.wiki/w/%E5%9F%BA%E5%B2%A9%E7%89%88%E5%AD%98%E6%A1%A3%E6%A0%BC%E5%BC%8F/%E7%89%A9%E5%93%81%E6%A0%BC%E5%BC%8F#%E5%B8%B8%E8%A7%84%E6%A0%87%E7%AD%BE)
```
"item_lock": {
"type": "panel",
"bindings": [{
"binding_type": "view",
"source_property_name": "(not (#lock_mode = 0))",
"target_property_name": "#visible"
}],
"property_bag": {
"#lock_mode": 0
},
"controls": [
{"lock_red@common.container_item_lock_red": {
"bindings": [{
"binding_type": "view",
"source_control_name": "item_lock",
"resolve_sibling_scope": true,
"source_property_name": "(#lock_mode = 1)",
"target_property_name": "#visible"
}]
}},
{"lock_yellow@common.container_item_lock_yellow": {
"bindings": [{
"binding_type": "view",
"source_control_name": "item_lock",
"resolve_sibling_scope": true,
"source_property_name": "(#lock_mode = 2)",
"target_property_name": "#visible"
}]
}}
]
}
```
这里使用了表达式这样Python端只需要传回itemDict的userData里的item_lock对应的值即可不需要考虑具体控件的显示和隐藏
## 效果展示
如图所示为季度mod西游大闹天宫中图鉴的效果展示下面我们将一步步拆解通过一个demo复刻来详细介绍如何实现ui数据绑定
如图所示为季度mod西游大闹天宫中图鉴的效果展示下面我们将一步步拆解通过一个demo复刻来详细介绍如何使用ui数据绑定
![分页](./picture/dataBindingUI/fenye.gif)
## toggle控件
### toggle控件
toggle控件继承自原版的common.toggle可以理解为开关或者选项控件可用于设置中的单选复选其中我们是选用单选toggle来实现分页效果具体mod及源码可参考示例中的DataBindingMod
## stack_grid控件
### stack_grid控件
该控件结合了stack_panel和grid可以理解它就是一个一维或横向扩展或纵向扩展的grid通过绑定collection可以快速实现类似记分牌列表展示等功能本例中三级分页tab列表便采用了该控件
## UI节点树
### UI节点树
实际上在本例中红框中的三个部分都采用了toggle实现了三级分页效果
@@ -54,13 +387,9 @@ toggle控件继承自原版的common.toggle可以理解为开关或者选项
- itemHeaderContentPanel 物品图鉴panel
- ...物品图鉴结构同生物图鉴不做重复介绍
### Tab分页的实现
## 具体用例
### tab分页的简单实现
上述效果展示中的例子因为是一个三级分页我们分两部分来介绍首先来介绍一下第一级和第二级这种固定的分页以第二级的tab为例其json代码如下
上述效果展示中的例子因为是一个三级分页我们分两部分来介绍首先来介绍一下第一级和第二级这种固定的分页以第二级的tab为例其JSON代码如下
```json
{
@@ -143,9 +472,7 @@ toggle控件继承自原版的common.toggle可以理解为开关或者选项
}
```
这里直接继承了ui_common.json中微软封装的toggle组件具体字段的含义可参考注释其中$toggle_name所绑定的参数在python中进行管理
这里直接继承了ui_common.json中微软封装的toggle组件具体字段的含义可参考注释其中$toggle_name所绑定的参数在Python中进行管理
```python
@ViewBinder.binding(view_binder.BF_ToggleChanged, "#typeTab")
@@ -156,8 +483,7 @@ def OnTypeTabChecked(self, args):
```
从json中我们可以看到toggle总共有八种状态各种状态的详细效果可以打开游戏设置界面查看左侧的按钮就是toggle。主要有选中、未选中两种基础状态每种状态又有hover和非hover然后还有锁定、未锁定所以总共就有2^3=8种状态组合需要不同的控件展示。由于我们这里比较简单只考虑了选中、未选中以及锁定三种状态所以只实现了对应的三种控制panel其json如下
从JSON中我们可以看到toggle总共有八种状态各种状态的详细效果可以打开游戏设置界面查看左侧的按钮就是toggle主要有选中未选中两种基础状态每种状态又有hover和非hover然后还有锁定未锁定所以总共就有2^3=8种状态组合需要不同的控件展示。由于我们这里比较简单只考虑了选中、未选中以及锁定三种状态所以只实现了对应的三种控制panel其JSON如下
```json
{
@@ -244,21 +570,15 @@ def OnTypeTabChecked(self, args):
}
```
这里把三种状态的label统一通过$buttonLabel来管理这样在子类中上面的每个具体的toggle只用管理这一个参数就行。另外各个状态的贴图和字体颜色也是分开控制的细心的朋友可能发现了选中状态多了个checkedImg锁定的状态多了个lockedImg前者是用来实现选中时按钮下方的小箭头的而后者则是用来实现锁的图标的。是否锁定只需要设置其*enabled*字段就可以也可以在python中通过调用SetTouchEnable接口来动态设置。效果如图所示
这里把三种状态的label统一通过$buttonLabel来管理这样在子类中上面的每个具体的toggle只用管理这一个参数就行另外各个状态的贴图和字体颜色也是分开控制的细心的朋友可能发现了选中状态多了个checkedImg锁定的状态多了个lockedImg前者是用来实现选中时按钮下方的小箭头的而后者则是用来实现锁的图标的是否锁定只需要设置其*enabled*字段就可以也可以在Python中通过调用SetTouchEnable接口来动态设置效果如图所示
![toggle_state](./picture/dataBindingUI/tujian3.png)
以上即为最简单的分页实现
### 自适应tab列表
### 高度数据绑定的自适应tab列表
接下来以第三级分页来介绍数据绑定我们先来看看对应的json
接下来以第三级分页来介绍数据绑定我们先来看看对应的JSON
```json
{
@@ -352,9 +672,7 @@ def OnTypeTabChecked(self, args):
}
```
我们先看toggleBtn的实现与上面的二级tab相比三级tab没有锁定的状态因此只用了选中和未选中两种状态panel。两个panel简单改变了不同的背景颜色和字体颜色重点在itemLabel通过绑定collection在python中可以绑定对应的数据
我们先看toggleBtn的实现与上面的二级tab相比三级tab没有锁定的状态因此只用了选中和未选中两种状态panel两个panel简单改变了不同的背景颜色和字体颜色重点在itemLabel通过绑定collection在Python中可以绑定对应的数据
```python
@ViewBinder.binding_collection(view_binder.BF_BindString, "itemToggles", "#label_name.text")
@@ -363,9 +681,7 @@ def OnRefreshItemLabel(self, index):
return self.mItemData[self.mCurItemTab][index]["name"] if len(self.mItemData[self.mCurItemTab]) > index else ""
```
我们再来看stack_grid的实现重点就在于collection_name的绑定和#StackGridItemsCount的绑定,这里通过#itemToggleGrid.item_count来复盖#StackGridItemsCount并在python中实现动态绑定
我们再来看stack_grid的实现重点就在于collection_name的绑定和#StackGridItemsCount的绑定这里通过#itemToggleGrid.item_count来复盖#StackGridItemsCount并在Python中实现动态绑定
```python
@ViewBinder.binding(view_binder.BF_BindInt, "#itemToggleGrid.item_count")
@@ -374,8 +690,6 @@ def OnItemGridResize(self):
return len(self.mItemData[self.mCurItemTab])
```
最后就是点击item列表之后的回调了
```python
@@ -393,8 +707,6 @@ def OnItemToggleChecked(self, args):
self.set_control_visible(self.mItemInfoWrap, True)
```
## 总结
### 单选toggle