@@ -1188,12 +1188,6 @@ JSON如下:
"size" : [ 100 , 100 ],
"offset" : "@UIDemo.animation_in" ,
"texture" : "textures/netease/common/image/default" ,
"is_new_nine_slice" : false ,
"nine_slice_buttom" : 0 ,
"nine_slice_left" : 0 ,
"nine_slice_right" : 0 ,
"nine_slice_top" : 0 ,
"nineslice_size" : [ 0 , 0 , 0 , 0 ],
"type" : "image"
},
"animation_in" : {
@@ -1246,104 +1240,6 @@ button是按钮控件, 按钮有四种状态, 分别为default/hover/pressed/l
下面是一个使用了三种状态的按钮, 我们继承common.button里的属性, 无需自己再定义default_control等属性。
```json
{
"button0@common.button" : {
"$default_texture" : "textures/netease/common/button/default" ,
"$hover_texture" : "textures/netease/common/button/hover" ,
"$is_new_nine_slice" : false ,
"$label_color" : [ 1 , 1 , 1 ],
"$label_font_scale_factor" : 1.0 ,
"$label_font_size" : "large" ,
"$label_layer" : 3 ,
"$label_offset" : [ 0 , 0 ],
"$label_text" : "Button" ,
"$nine_slice_buttom" : 0 ,
"$nine_slice_left" : 0 ,
"$nine_slice_right" : 0 ,
"$nine_slice_top" : 0 ,
"$nineslice_size" : [ 0 , 0 , 0 , 0 ],
"$pressed_button_name" : "%fpsBattle.click" ,
"$pressed_texture" : "textures/netease/common/button/pressed" ,
"$texture_layer" : 2 ,
"anchor_from" : "center" ,
"anchor_to" : "center" ,
"is_handle_button_move_event" : true ,
"button_mappings" : [],
"bindings" : [
{
"binding_collection_name" : "" ,
"binding_condition" : "always_when_visible" ,
"binding_type" : "collection_details"
}
],
"controls" : [
{
"default@fpsBattle.default" : {}
},
{
"hover@fpsBattle.hover" : {}
},
{
"pressed@fpsBattle.pressed" : {}
},
{
"button_label@fpsBattle.button_label" : {}
}
],
"default_control" : "default" ,
"hover_control" : "hover" ,
"layer" : 3 ,
"offset" : [ 0 , 0 ],
"pressed_control" : "pressed" ,
"size" : [ 100 , 50 ],
"type" : "button" ,
"visible" : true
},
"button_label" : {
"color" : "$label_color" ,
"font_scale_factor" : "$label_font_scale_factor" ,
"font_size" : "$label_font_size" ,
"font_type" : "smooth" ,
"layer" : "$label_layer" ,
"max_size" : [ "100%" , "100%" ],
"offset" : [ 0 , 0 ],
"shadow" : false ,
"text" : "$label_text" ,
"text_alignment" : "center" ,
"type" : "label"
},
"default" : {
"is_new_nine_slice" : "$is_new_nine_slice" ,
"layer" : "$texture_layer" ,
"nine_slice_buttom" : "$nine_slice_buttom" ,
"nine_slice_left" : "$nine_slice_left" ,
"nine_slice_right" : "$nine_slice_right" ,
"nine_slice_top" : "$nine_slice_top" ,
"texture" : "$default_texture" ,
"type" : "image"
},
"hover" : {
"is_new_nine_slice" : "$is_new_nine_slice" ,
"layer" : "$texture_layer" ,
"nine_slice_buttom" : "$nine_slice_buttom" ,
"nine_slice_left" : "$nine_slice_left" ,
"nine_slice_right" : "$nine_slice_right" ,
"nine_slice_top" : "$nine_slice_top" ,
"texture" : "$hover_texture" ,
"type" : "image"
},
"pressed" : {
"is_new_nine_slice" : "$is_new_nine_slice" ,
"layer" : "$texture_layer" ,
"nine_slice_buttom" : "$nine_slice_buttom" ,
"nine_slice_left" : "$nine_slice_left" ,
"nine_slice_right" : "$nine_slice_right" ,
"nine_slice_top" : "$nine_slice_top" ,
"texture" : "$pressed_texture" ,
"type" : "image"
}
}
```
"button0@common.button" : {
"size" : [ 100 , 50 ],
"button_mappings" : [], //和$pressed_button_name只能二选一
@@ -2372,7 +2268,7 @@ hover事件的触发本质上是与鼠标悬浮相关, 在PC模式中, 当鼠
UI编辑器暂时不支持编辑
### M iniM ap
### m ini\_m ap
该控件可以在UI上画小地图, 需要继承mini_map命名空间下的mini_map_wrapper控件。
@@ -2423,290 +2319,19 @@ UI编辑器暂时不支持编辑
| $face_icon_size | 脸部icon的大小, 默认为[4,4] |
| $face_icon_bg_color | 标记icon底部背景颜色, 默认为白色 |
| $highest_y | 绘制的高度最大值,默认当前区块的最大值,当该值为-1时, 表示最大高度值为玩家当前位置所在的高度 |
| enable_scissor_test | 超出父控件区域后是否裁剪, 默认为false, [enable_scissor_test ](https://wiki.bedrock.dev/json-ui/json-ui-documentation.html )|
### **颜色渐变控件( gradientRenderer) **
该控件可以用于在ui上绘制渐变颜色
```json
{
"gradient" : {
"color1" : [ 1 , 0 , 1 , 1 ],
"color2" : [ 0 , 0 , 1 , 1 ],
"gradient_direction" : "vertical" ,
"renderer" : "gradient_renderer" ,
"type" : "custom"
}
}
```
| 变量 | 解释 |
| ------------------ | -------------------------------------- |
| color1 | 渐变起始颜色的RGBA |
| color2 | 渐变结束颜色的RGBA |
| gradient_direction | 渐变方向,可选"vertical"或"horizontal" |
| renderer | 需要指定为gradient_renderer |
| type | 需要指定为custom |
< img src = "./picture/IntroduceUI/IntroduceUI-50.png" alt = "图片描述" style = "display: block; margin-left: 0;" />
下图为UI编辑器中颜色渐变控件的属性编辑面板
< img src = "./picture/IntroduceUI/IntroduceUI-51.png" alt = "图片描述" style = "display: block; margin-left: 0;" />
| 变量 | 解释 |
| ------------ | ------------------------------------------------------ |
| 渐变起始颜色 | 对应color1字段, 支持调整不透明度( Alpha通道) |
| 渐变结束颜色 | 对应color2字段, 支持调整不透明度( Alpha通道) |
| 渐变方向 | 对应gradient_direction字段, 可选“垂直排布”或“水平排布” |
### 继承控件
继承控件允许开发者选择并继承目标控件,继承成功后该控件拥有目标控件的所有属性,并可以重写其中任何一个属性的数据。
#### 继承写法简述
在界面json文件所有的编写技巧中, 最为好用和灵活的功能当属继承写法。当界面中有一个需求, 需要将若干个相同的控件按序排列, 除了可以通过复制粘贴出若干个控件副本外, 继承模板控件并只修改我们所需要修改的属性, 其他的属性依然沿用模板控件的数据才是最便捷, 也是最漂亮的写法。下面我们从一个简单的例子入手熟悉继承的写法, 从例子中我们可以快速熟悉继承技巧。
```json
{
"main" : {
"absorbs_input" : true ,
"always_accepts_input" : false ,
"controls" : [
{
"label0@myInherit.label0" : {}
},
{
"inheritor0@myInherit.label0" : {
"offset" : [ 10.0 , 0.0 ]
}
},
{
"inheritor1@myInherit.label0" : {
"offset" : [ 20.0 , 0.0 ]
}
}
],
"force_render_below" : false ,
"is_showing_menu" : true ,
"render_game_behind" : true ,
"render_only_when_topmost" : true ,
"should_steal_mouse" : false ,
"type" : "screen"
},
"label0" : {
"anchor_from" : "center" ,
"anchor_to" : "center" ,
"color" : [ 1 , 1 , 1 ],
"font_scale_factor" : 1.0 ,
"font_size" : "normal" ,
"font_type" : "smooth" ,
"layer" : 0 ,
"offset" : [ 0 , 0 ],
"shadow" : false ,
"size" : [ 100 , 100 ],
"text" : "Hello World!" ,
"text_alignment" : "center" ,
"type" : "label" ,
"visible" : true
},
"namespace" : "myInherit"
}
```
该段json描述了在main画布中创建了一个文本控件label0, 并使继承控件inherit0和inherit1均继承了label0控件, 并重写了offset属性, 在场景中就得到了三个文本控件, 这三个文本控件除了在场景中的位置因为重写而不同外, 其他的属性一模一样。但是要注意的是, 可以被继承的控件必须写在json文件的最外层, 和main处在同一层级, 即一个命名空间下有且仅有一个该名称的控件, 满足该条件的控件才可以被继承。
#### UI编辑器中的继承
新版的UI编辑器对继承提供了更可视化的方法, 请参考[继承和自定义控件 ](./13-继承和自定义控件.md )。
## Python编写说明
### 必要的属性
```python
import mod.client.extraClientApi as clientApi
ViewBinder = clientApi . GetViewBinderCls ()
ViewRequest = clientApi . GetViewViewRequestCls ()
ScreenNode = clientApi . GetScreenNodeCls ()
```
| 变量 | 解释 |
| :------------: | :----------------------------------- |
| extraClientApi | 我们开发的Client端Api接口文件 |
| ViewBinder | 用于绑定回调函数的类型和响应的方法 |
| ViewRequest | 用于返回绑定函数的返回值 |
| ScreenNode | UI的基类, 用于继承基类的方法和UI管理 |
### UI界面初始化
```python
import mod.client.extraClientApi as clientApi
ScreenNode = clientApi . GetScreenNodeCls ()
class TestScreen ( ScreenNode ):
def __init__ ( self , namespace , name , param ):
ScreenNode . __init__ ( self , namespace , name , param )
```
ScreenNode是我们的UI节点基类, 必须继承。
```python
# Bind Type
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
# Return Type
class ViewRequest ( object ):
Nothing = 0
Refresh = 1 << 0
PointerHeldEventsRequest = 1 << 1
PointerHeldEventsCancel = 1 << 2
Exit = 1 << 3
```
### UI绑定和返回
UI的绑定分为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 | 开关状态改变事件 |
**binding(bind_flag, binding_name = None)**
bind_flag为上文中绑定类型, binding_name为绑定名称。
binding_name为脚本绑定变量, binding_name_override为引擎变量, json格式如下
```json
{
"bindings" : [
{
"binding_condition" : "always" ,
"binding_name" : "#scoreboard_grid.item_count" ,
"binding_name_override" : "#StackGridItemsCount"
}
]
}
```
对应的Python代码如下
```python
import mod.client.extraClientApi as clientApi
ViewBinder = clientApi . GetViewBinderCls ()
@ViewBinder.binding ( ViewBinder . BF_BindInt , "#scoreboard_grid.item_count" )
def OnStarkGridResize ( self ):
return len ( self . scoreBoardList )
```
**binding_collection(bind_flag, collection_name, binding_name = None)**
bind_flag为上文中的绑定类型, collection_name为集合名称, binding_name为绑定的变量名称。
集合的json如下:
```json
{
"collection_name" : "scoreboard_stackgrid"
}
```
在集合的子控件中, binding_collection_name为集合名, binding_condition为绑定条件, binding_name为绑定名称, binding_type为collection绑定, property_bag设置他的初始值, text为它的绑定值。
```json
{
"label_score_board" : {
"bindings" : [
{
"binding_collection_name" : "scoreboard_stackgrid" ,
"binding_condition" : "always" ,
"binding_name" : "#label_score_board.text" ,
"binding_type" : "collection"
}
],
"offset" : [ "0%+0 px" , "0%+0px" ],
"property_bag" : {
"#label_score_board.text" : "666666666666"
},
"text" : "#label_score_board.text" ,
"text_alignment" : "left" ,
"type" : "label" ,
"visible" : true
}
}
```
对应的Python代码如下, 其中index表示在集合中的哪一元素。
```python
import mod.client.extraClientApi as clientApi
ViewBinder = clientApi . GetViewBinderCls ()
@ViewBinder.binding_collection ( ViewBinder . BF_BindString , "scoreboard_stackgrid" , "#label_score_board.text" )
def OnRefreshScoreBoardLabel ( self , index ):
return self . scoreBoardList [ index ] if len ( self . scoreBoardList ) > index else ""
```
## 接口调用说明
### 参数命名说明
@Mod .Binding(name = myModName, version = myModVersion)
| 参数 | 类型 | 解释 |
| :----------: | :--: | :------ |
| myModName | str | Mod名称 |
| myModVersion | str | Mod版本 |
假设设置Mod名称为"myModName",示例:
```python
from mod.common.mod import Mod
@Mod.Binding ( name = "myModName" , version = "0.1" )
class MyModClass ( object ):
def __init__ ( self ):
pass
```
### 界面创建流程及生命周期
### 界面创建流程
界面在< a href = "../../mcdocs/1-ModAPI/事件/UI.html #uiinitfinished " rel = "noopenner" > UiInitFinished </ a > 事件发送之后就可以开始创建
创建UI的第一步是UI的注册, 通过调用< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #registerui " rel = "noopenner" > RegisterUI </ a > 接口,将想开启UI的相关数据进行注册。 同一UI只需要注册一次即可
创建UI的第一步是UI的注册, 通过调用< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #registerui " rel = "noopenner" > RegisterUI </ a > 接口,传入UI类路径和JSON控件路径注册UI, 同一UI只需要注册一次即可。
注册完成之后就可以在需要的时候实例化UI, 可使用的接口有< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #createui " rel = "noopenner" > CreateUI </ a > 和< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #pushscreen " rel = "noopenner" > PushScreen </ a > 两种。使用< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #createui " rel = "noopenner" > CreateUI </ a > 生成的界面都生成在操作UI下, 不同的生成参数UI层级不同, 而使用< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #pushscreen " rel = "noopenner" > PushScreen </ a > 生成的界面以堆栈管理的方式生成,该界面生成时上一个< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #pushscreen " rel = "noopenner" > PushScreen </ a > 生成的界面就会被隐藏。值得注意的是, MC原生界面( 比如暂停界面, 背包界面等) 也是类似< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #pushscreen " rel = "noopenner" > PushScreen </ a > 的方式加载进来,并以堆栈管理。
在UI创建完成之后, 可以调用< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #getui " rel = "noopenner" > GetUI </ a > 接口拿到UI对应的ScreenNode实例。
如果想获取MC原生界面的实例, 则需要监听原生界面Push进来的事件< a href = "../../mcdocs/1-ModAPI/事件/UI.html #pushscreenevent " rel = "noopenner" > PushScreenEvent </ a > ,然后在事件回调中调用接口< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #gettoppushedui " rel = "noopenner" > GetTopPushedUI </ a > 。
每个ScreenNode都有生命周期函数, 生命周期函数会被自动在以下情况下调用, 重写函数可以完成一些逻辑。
@@ -2716,9 +2341,38 @@ class MyModClass(object):
| < a href = "../../mcdocs/1-ModAPI/接口/自定义UI/UI界面.html #create " rel = "noopenner" > Create </ a > | UI创建成功时调用 |
| < a href = "../../mcdocs/1-ModAPI/接口/自定义UI/UI界面.html #onactive " rel = "noopenner" > OnActive </ a > | UI重新回到栈顶时调用 |
| < a href = "../../mcdocs/1-ModAPI/接口/自定义UI/UI界面.html #ondeactive " rel = "noopenner" > OnDeactive </ a > | 栈顶UI有其他UI入栈时调用 |
| < a href = "../../mcdocs/1-ModAPI/接口/自定义UI/UI界面.html #update " rel = "noopenner" > Update </ a > | 客户端每帧调用, 1秒有30帧 |
| < a href = "../../mcdocs/1-ModAPI/接口/自定义UI/UI界面.html #destroy " rel = "noopenner" > Destroy </ a > | UI销毁时调用 |
最后, 当UI需要销毁时, 可以调用ScreenNode实例的< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/UI界面.html #setremove " rel = "noopenner" > SetRemove </ a > 接口,此外使用< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #pushscreen " rel = "noopenner" > PushScreen </ a > 接口创建的界面还可以使用< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #popscreen " rel = "noopenner" > PopScreen </ a > 接口进行销毁。
### 编写UI类
UI类用于编写界面逻辑, 需要在< a href = "../../mcdocs/1-ModAPI/接口/自定义UI/通用.html #registerui " rel = "noopenner" > RegisterUI </ a > 时传入UI类的路径。一个客户端类可以对应多个UI类。UI类一般包含以下内容:
```python
import client.extraClientApi as clientApi
ScreenNode = clientApi . GetScreenNodeCls ()
ViewBinder = clientApi . GetViewBinderCls ()
ViewRequest = clientApi . GetViewViewRequestCls ()
Client = clientApi . GetSystem ( 'xxxxMod' , 'xxxxClientSystem' )
CF = clientApi . GetEngineCompFactory ()
PID = clientApi . GetLocalPlayerId ()
class uiName ( ScreenNode ):
def __init__ ( self , namespace , name , param ):
ScreenNode . __init__ ( self , namespace , name , param )
def Create ( self ):
pass
def Destroy ( self ):
pass
def Update ( self ):
pass
```
| 变量 | 解释 |
| :------------: | :----------------------------------- |
| extraClientApi | 我们开发的Client端Api接口文件 |
@@ -2858,11 +2512,9 @@ button_data: {
在字符串中嵌入`<link>link_data</link>` 样式会被解析成超链接, 外观和普通文本无异但可以被点击, link_data为特殊化数据, 开发者可以在其中添加符合JSON格式的自定义数据, 在点击富文本中对应超链接时会将link_data通过回调函数整个返回, 以下属性为必须属性, text表示显示文本, format_code表示该段显示文本的样式代码, 注意, 基岩版的文本控件不支持下划线和删除线。
```json
{
"link_data" :{
"text " : "末影人" ,
"format_code" : "§2"
}
link_data: {
"text" : "末影人" ,
"format_code " : "§2"
}
```