UI使用规范
文件命名规范json文件名 json中的namespace python文件名 三者需要一致
示例中为"myUIName"x
myUIName.json"namespace" : "myUIName"myUIName.py
JSON规范
Json是UI界面的显示内容和绑定的集合,UI是一种树状的组织结构,界面和界面中的控件都是一个UI Node节点,后续的很多方法的调用也需要这个树状结构的路径来标识。注:暂时Json只能通过手写来构筑界面,后续会开放相应的UI编辑器供开发者使用。
命名空间
命名空间是这个界面的名称,我们规定json文件名和namespace一致。xxxxxxxxxx
"namespace" : "myUIName"
Main Screen
main是这个界面名称,我们规定使用main,即一个json文件是一个界面。
controls中的内容是该界面下的树状节点,即子节点。xxxxxxxxxx
"main@common.base_screen" : {"controls" : []}变量解释main@common.base_screen表示main screen是common.base_screen的一个子类xxxxxxxxxx
my_namespace|____main|test_image|test_panel|test_label关于控件的适配问题,固定大小像素能在屏幕像素发生变化时保持形状大小不变,而百分比则能够保持与屏幕分辨率一致的比例,可能导致控件变形。
控件介绍
Label
Label 是文本框控件,用来显示文本信息,默认的字体是MC字体,如果有中文会变成中文字体格式,暂时不支持换字体,可以通过接口统一设置字体,如果需要设置后续有介绍。xxxxxxxxxx
"label" : {"anchor_from" : "top_left","anchor_to" : "top_left","auto_expand" : false,"color" : [ 1, 1, 0, 0.8],"font_size" : "large","layer" : 5,"offset" : [ "0%+0px", "0%+0px" ],"shadow" : false,"size" : [ "83.80282%+0px", "100%+0px" ],"text" : "Hello World","text_alignment" : "center","type" : "label","visible" : true},变量解释anchor_from挂接在父节点锚点的位置,取值范围["top_left", "top_middle", "top_right", "left_middle", "center", "right_middle", "bottom_left", "bottom_middle", "bottom_right"]
anchor_to自身挂接锚点的位置,取值同anchor_from
auto_expand该变量暂时无效,可删除
color字体颜色(r , g, b, a)取值范围[0 , 1]
font_size字体大小,取值["large", "normal", "small"]
layerlabel的相对父节点的层级,最终显示层级取决于父节点到该节点的layer之和
offset自身相对父节点的偏移,值为百分比+像素(1个像素等于屏幕上3个像素)
shadowMC自带的字体阴影,true为显示,false则不显示
size相对父节点的大小,取值同offset
text该Label显示的内容,可以通过后续的API在代码中设置该值
text_alignmentLabel中文字的对齐方式,取值为["left", "center", "right"]
typelabel类型
visible默认是否显示,true为显示,false为不显示
Image
Iamge是在UI中的图片控件,可以动态设置控件的图片,该种控件的特点是会保持图片的形状进行拉伸,重复的变量不再重复解释。xxxxxxxxxx
"image" : {"anchor_from" : "center","anchor_to" : "center","layer" : 1,"nineslice_size" : 0,"offset" : [ 0, 0 ],"size" : [ "20.000000%+0 px", "20.000000%+0px" ],"texture" : "textures/fish","type" : "image","uv" : [ 0, 0 ],"uv_size" : [ 16, 16 ],"visible" : true}变量解释nineslice_size九宫格切片的大小,image类型为0
texture贴图的路径,该路径从resouce_pack中的textures目录开始
type类型为image
uvuv坐标的初始值为[0, 0]
NineSliceImage
NineSliceImage是指九宫格图片空间,可以动态设置控件图片,该控件相比Image会随着拉伸改变九宫格的形状,该九宫格只显示切片后中心宫的内容。xxxxxxxxxx
"nine_slice_iamge" : {"anchor_from" : "center","anchor_to" : "center","is_new_nine_slice" : true,"layer" : 1,"nine_slice_bottom" : 0,"nine_slice_left" : 0,"nine_slice_right" : 0,"nine_slice_top" : 0,"offset" : [ 0, 0 ],"size" : [ "20.000000%+0 px", "30.000000%+0px" ],"texture" : "textures/items/fish","type" : "image","visible" : true,}变量解释is_new_nine_slice设置为true标记该图片为NineSliceImage类型图片
nine_slice_bottom切片距离下边的距离,默认值为0
nine_slice_left切片距离左边的距离,默认值为0
nine_slice_right切片距离右边的距离,默认值为0
nine_slice_top切片距离上边的距离,默认值为0
Image Button
ImageButton 是指可以动态设置贴图的按钮,同时含有NineSliceImage和Label。按钮有三种状态,分别为default/hover/pressed,可以分别对应不同的贴图。注 $符号表示定义变量
controls表示控制的子节点,xxxxxxxxxx
"button@common.button" : {"$default_color" : [ 1, 1, 1 ],"$default_texture" : "textures/common/button/btn01","$hover_texture" : "textures/common/button/btn02","$pressed_texture" : "textures/common/button/btn03","$labelText" : "hello","$label_font_size" : "large","$nine_slice_bottom" : 0,"$nine_slice_left" : 0,"$nine_slice_right" : 0,"$nine_slice_top" : 0,"$text_offset" : [ 0, 0 ],"anchor_from" : "center","anchor_to" : "center","is_handle_button_move_event": true,"button_mappings": [],"controls" : [{"default" : {"is_new_nine_slice" : true,"layer" : 2,"nine_slice_bottom" : "$nine_slice_bottom","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" : true,"layer" : 2,"nine_slice_bottom" : "$nine_slice_bottom","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" : true,"layer" : 2,"nine_slice_bottom" : "$nine_slice_bottom","nine_slice_left" : "$nine_slice_left","nine_slice_right" : "$nine_slice_right","nine_slice_top" : "$nine_slice_top","texture" : "$pressed_texture","type" : "image"}},{"button_label" : {"color" : "$default_color","font_size" : "$label_font_size","layer" : 3,"max_size" : [ "100%", "100%" ],"offset" : "$text_offset","shadow" : false,"text" : "$labelText","type" : "label"}}],"layer" : 1,"offset" : [ 0, 0 ],"size" : [ "15.000000%+0 px", "10.000000%+0px" ],"visible" : true}变量解释default表示按钮默认状态下的显示的图片内容
hover表示按钮处于悬浮状态下的图片内容
pressed表示按钮处于按下状态下的图片内容
button@common.button表示该节点是一个common.button的子类
Panel
panel为面板控件,主要是用来将控件进行分类和管理,类似文件夹。xxxxxxxxxx
"panel" : {"anchor_from" : "center","anchor_to" : "center","layer" : 0,"offset" : [ 0, 0 ],"size" : [ "50.000000%+0 px", "50.000000%+0px" ],"type" : "panel","visible" : true}变量解释type类型为panel
TextEditBox
TextEditBox是输入框控件,用来输入文字信息,可以获取输入内容,设置输入框内容,触发输入中和输入完成事件,设置最大输入值等。下面的示例展示了一个搜索框的信息。xxxxxxxxxx
"search_panel": {"type": "panel","anchor_from" : "top_left","anchor_to" : "top_left","offset" : [ "7.21832%+0px", "50%+0px" ],"size" : [ "83.80282%+0px", "10.31250%+0px" ],"$text_edit_clipping_panel_size": [ "100% - 23px", "100%" ],"$text_edit_box_label_anchor_point": "left_middle","controls": [{"search_text_box@common.text_edit_box": {"max_length": 3,"$text_box_name": "%test_screen.TextBox","$text_edit_box_enabled_binding_type": "none","$text_edit_box_content_binding_name": "#test_screen.ReturnTextString","$place_holder_text": "#test_screen.ReturnHolderContent","$text_edit_box_placeholder_content_binding_name": "#test_screen.ReturnHolderContent","$enabled": true}},{"clear_button@common.close_button": {"anchor_from": "right_middle","anchor_to": "right_middle","visible": true,"layer": 2,"focus_enabled": true,"$close_button_offset": [ -2, 0 ],"button_mappings": [{"from_button_id": "button.menu_select","to_button_id": "%test_screen.ClearButtonClick","mapping_type": "pressed"},{"from_button_id": "button.menu_ok","to_button_id": "%test_screen.ClearButtonClick","mapping_type": "focused"}]}},{"test_label@test_screen.test_label" : {}}]}变量解释search_text_box@common.text_edit_box继承于基础的text_edit_box输入框
max_length初始最大输入长度,后续可代码设置
"$text_box_name": "%test_screen.TextBox"获取输入到的信息,监听了BF_EditChanged和BF_EditFinished的函数Textbox,会在输入框内容修改和输入完成时调到该函数,可参考下面的注
"$text_edit_box_content_binding_name": "#test_screen.ReturnTextString"输入框显示ReturnTextString中返回的内容,这与上面形成了一个双向绑定,可参考下面的注
"$place_holder_text": "#test_screen.ReturnHolderContent"输入框初始化没有输入时的提示语,函数ReturnHolderContent返回我们想要显示的内容,可参考下面的注
"to_button_id": "%test_screen.ClearButtonClick"清除按钮的逻辑,清除输入框中的内容,可参考下面的注注1xxxxxxxxxx
class TestScreen(ScreenNode):def __init__(self, namespace, name, param):ScreenNode.__init__(self, namespace, name, param)self.text = ""self.holder = str("请输入姓名")@ViewBinder.binding(ViewBinder.BF_EditChanged | ViewBinder.BF_EditFinished)def TextBox(self, args):print "SearchTextBox ", argsself.text = args["Text"]return ViewRequest.Refresh@ViewBinder.binding(ViewBinder.BF_InteractButtonClick)def ClearButtonClick(self, args):self.text = ""return ViewRequest.Refresh@ViewBinder.binding(ViewBinder.BF_BindString)def ReturnTextString(self):return self.text@ViewBinder.binding(ViewBinder.BF_BindString)def ReturnHolderContent(self):return self.holder注2
max_length 可以通过接口SetEditTextMaxLength,接口详细调用可见下文。
注意:输入框不允许在inputmode为0的情况下使用。
创建带输入框的UI时inputmode必须要为1,如:
clientApi.CreateUI("testMod", "testUI", {"inputMode":1})
PaperDoll
该控件可以用于在ui上显示骨骼模型xxxxxxxxxx
"paper_doll0" : {"anchor_from" : "center","anchor_to" : "center","bindings" : [{"binding_type": "view","source_control_name": "paper_doll0_skin_viewer_panel","source_property_name": "#gesture_delta_source","target_property_name": "#gesture_delta_source"},{"binding_type" : "view","source_control_name" : "paper_doll0_skin_viewer_panel","source_property_name" : "#gesture_mouse_delta_x","target_property_name" : "#gesture_mouse_delta_x"}],"controls" : [{"paper_doll0_skin_viewer_panel" : {"anchor_from" : "bottom_middle","anchor_to" : "bottom_middle","button_mappings" : [{"button_up_right_of_first_refusal" : true,"from_button_id" : "button.menu_select","mapping_type" : "pressed","to_button_id" : "button.turn_doll"}],"gesture_tracking_button" : "button.turn_doll","layer" : 3,"offset" : [ 0, 0 ],"size" : [ "100%+0px", "100%+0px" ],"type" : "input_panel","visible" : true}}],"property_bag":{"#skin_rotation": true,"#custom_rot_y": 90},"layer" : 1,"offset" : [ "0.00000%+0px", "1.66667%+0px" ],"renderer" : "paper_doll_renderer","rotation" : "gesture_x","modelname": "xuenv","animation": "idle","modelsize": 1.0,"size" : [ "19.37500%+0px", "50.27777%+0px" ],"type" : "custom","visible" : true}变量解释modelname要显示的骨骼模型的名称,可通过API中的SetUiModel接口动态修改
animation骨骼模型播放的动作
modelsize骨骼模型的显示缩放
rotation模型的旋转控制,有以下取值:
auto或none: 模型根据#skin_rotation的取值是否自动旋转
custom_y: 模型根据#custom_rot_y的取值旋转固定角度
gesture_x: 模型可由用户拖动绕y轴任意旋转
bindings若rotation为gesture_x,需要有示例中的绑定,其中source_control_name与子controls的控件名对应
controls若rotation为gesture_x,需要有示例中的input_panel子控件,其中控件名,size,offset可自行调整,该子控件的size与offset决定模型拖动的响应区域
property_bag#skin_rotation:当rotation为auto或者none时生效,该属性为true,则模型自动缓慢旋转,为false时,模型正面朝屏幕
#custom_rot_y:当rotation为custom_y时生效,模型绕y轴旋转该值的角度
ScrollView
该控件是可以滑动的窗口,需要有其他控件附属。xxxxxxxxxx
"first_scroll_panel@common.scrolling_panel" : {"$scroll_bar_right_padding_size" : [ 0, 3 ],"$scroll_box_mouse_image_control" : "common-classic.button_state_default","$scroll_size" : [ 0, "100.000000%+0px" ],"$scroll_view_name" : "scroll_view","$scrolling_content" : "test_screen.first_stack_grid","$scrolling_pane_offset" : [ -3, 0 ],"$scrolling_pane_size" : [ "100.000000%+0 px", "100.000000%+0px" ],"$show_background" : false,"anchor_from" : "right_middle","anchor_to" : "right_middle","layer" : 2,"offset" : [ "-9.500000%+0 px", "3.000000%+0px" ],"size" : [ "37.67606%+0px", "66.87500%+0px" ],"visible" : true},变量解释scroll_bar_right_padding_size滑动列表右侧填充的大小
scroll_box_mouse_image_controlcommon-classic.button_state_default为默认的状态
scrolling_content这里保存了该滑动窗口的内容
show_background是否显示背景
StackGrid
该控件可以依附在滚动条中,用来实现背包等功能。xxxxxxxxxx
"first_stack_grid" : {"anchor_from" : "center","anchor_to" : "center","bindings" : [{"binding_condition" : "always","binding_name" : "#first_stack_grid.item_count","binding_name_override" : "#StackGridItemsCount"}],"collection_name" : "hugo_first_scroll","controls" : [{"first_stack@test_screen.first_stack" : {}}],"item_count" : 8,"layer" : 0,"orientation" : "vertical","property_bag" : {"#first_stack_grid.item_count" : 8},"size" : [ "100%", "default" ],"type" : "stack_grid","visible" : true},变量解释"binding_name" : "#first_stack_grid.item_count"这里的binding_name属于在Python中使用的绑定变量
"binding_name_override" : "#StackGridItemsCount"这里的binding_name_override属于在引擎中的绑定变量,值不可改变
"collection_name" : "hugo_first_scroll"定义了集合变量的名称
item_count给定了默认的行数为8行
orientationvertical垂直地排列
Grid
Grid组件类似于上面的StackGrid组件,属于网格型的排列。xxxxxxxxxx
"grid0" : {"anchor_from" : "center","anchor_to" : "center","bindings" : [{"binding_condition" : "always","binding_name" : "#grid0.grid_dimension_binding","binding_name_override" : "#GridItemsCount"}],"collection_name" : "hugo_grid","grid_dimension_binding" : "#grid0.grid_dimension_binding","grid_dimensions" : [ 2, 2 ],"grid_item_template" : "test_screen.item_content0","layer" : 1,"offset" : [ "0%+0px", "0%+0px" ],"size" : [ "35.73944%+0px", "33.75000%+0px" ],"type" : "grid","visible" : true},变量解释"binding_name" : "#grid0.grid_dimension_binding"绑定脚本层的函数的名称
"binding_name_override" : "#GridItemsCount"引擎中绑定的名称
"grid_dimension_binding" : "#grid0.grid_dimension_binding",绑定函数的名称
"grid_dimensions" : [ 2, 2 ],初始值大小2X2
Python规范
必要的属性xxxxxxxxxx
import client.extraClientApi as clientApiViewBinder = clientApi.GetViewBinderCls()ViewRequest = clientApi.GetViewViewRequestCls()ScreenNode = clientApi.GetScreenNodeCls()变量解释extraClientApi我们开发的Client端Api接口文件
ViewBinder用于绑定回调函数的类型和响应的方法
ViewRequest用于返回绑定函数的返回值
ScreenNodeUI的基类,用于继承基类的方法和UI管理
UI界面初始化xxxxxxxxxx
class TestScreen(ScreenNode):def __init__(self, namespace, name, param):ScreenNode.__init__(self, namespace, name, param)
ScreenNode是我们的UI节点基类,必须继承。xxxxxxxxxx
# Bind Typeclass ViewBinder(object):ButtonFilter = 0x10000000BF_ButtonClickUp=0 | ButtonFilterBF_ButtonClickDown=1 | ButtonFilterBF_ButtonClick= 2 | ButtonFilterBF_ButtonClickCancel= 3BF_InteractButtonClick = 4BindFilter = 0x01000000BF_BindBool= 5 | BindFilterBF_BindInt= 6 | BindFilterBF_BindFloat= 7 | BindFilterBF_BindString= 8 | BindFilterBF_BindGridSize = 9 | BindFilterBF_BindColor= 10 | BindFilterEditFilter = 0x00100000BF_EditChanged= 11 | EditFilterBF_EditFinished= 12 | EditFilter# Return Typeclass ViewRequest(object):Nothing = 0Refresh = 1 << 0PointerHeldEventsRequest = 1 << 1PointerHeldEventsCancel = 1 << 2Exit = 1 << 3
UI绑定和返回
UI的绑定分为binding单个绑定和binding_collection集合绑定,适合集合容器。绑定类型绑定方式解释BF_ButtonClickUpbinding绑定按钮的Up事件
BF_ButtonClickDownbinding绑定按钮的Down事件
BF_ButtonClickbinding同时绑定Up和Down事件
BF_ButtonClickCancelbinding绑定按钮的Cancel事件(按钮down其他up)
BF_InteractButtonClickbinding绑定游戏原生的按钮点击事件
BF_BindBoolbinding|binding_collection绑定Bool变量
BF_BindIntbinding|binding_collection绑定Int变量
BF_BindFloatbinding|binding_collection绑定Float变量
BF_BindStringbinding|binding_collection绑定String变量
BF_BindGridSizebinding绑定GridSize变量
BF_BindColorbinding|binding_collection绑定颜色变量
BF_EditChangedbinding绑定输入框输入改变事件
BF_EditFinishedbinding绑定输入框输入完成事件
binding(bind_flag, binding_name = None)
bind_flag为上文中绑定类型,binding_name为绑定名称。
binding_name为脚本绑定变量,binding_name_override为引擎变量,json格式如下xxxxxxxxxx
"bindings" : [{"binding_condition" : "always","binding_name" : "#scoreboard_grid.item_count","binding_name_override" : "#StackGridItemsCount"}]
对应的Python代码如下xxxxxxxxxx
@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如下:xxxxxxxxxx
"collection_name" : "scoreboard_stackgrid"
在集合的子控件中,binding_collection_name为集合名,binding_condition为绑定条件,binding_name为绑定名称,binding_type为collection绑定,property_bag设置他的初始值,text为它的绑定值。xxxxxxxxxx
"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表示在集合中的哪一元素。xxxxxxxxxx
@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)参数类型解释myModNamestrMod名称
myModVersionstrMod版本
假设设置Mod名称为"myModName",示例:xxxxxxxxxx
@Mod.Binding(name = "myModName", version = "0.1")class MyModClass(object):def __init__(self):pass
注册UI界面
RegisterUI(myModName, key, clsPath, uiDef)参数类型解释myModNamestr用来标识Mod名称,尽量个性化不与其他人重复
keystr用来标识界面名称
clsPathstr用来标识python中ui类的路径
uiDefstr用来标识json ui的命名空间和界面名
该函数用来,示例:xxxxxxxxxx
import client.extraClientApi as clientApiclientApi.RegisterUI("myModName", "myUIName", "myScripts.modClient.ui.myUIName.MyUIClass", "myUIName.main")
创建UI界面
CreateUI(myModName, key, paramDict=None)参数类型解释myModNamestr用来标识Mod名称
keystr用来标识界面名称
paramDictdict参数字典 参数Key为( isHud) ,值为(0 / 1) ,意为是否为HUD界面的UI。一般情况下,射击按钮不屏蔽游戏, 原生的操作的界面应该isHud为1;商城界面等不适游戏内操作的界面isHud应该为0,默认值为1。xxxxxxxxxx
import client.extraClientApi as clientApiclientApi.CreateUI("myModName", "myUIName", {"isHud" : 1})
获取UI界面
GetUI(myModName, key)参数类型解释myModNamestr用来标识Mod名称
keystr用来标识界面名称
获取UI结点,该结点就是python类的实例,示例:xxxxxxxxxx
import client.extraClientApi as clientApiuiNode = clientApi.GetUI("myModName", "myUIName")
删除UI界面
uiNode.SetRemove()xxxxxxxxxx
import client.extraClientApi as clientApiuiNode = clientApi.GetUI("myModName", "myUIName")uiNode.SetRemove()xxxxxxxxxx
从上面获取的UI结点删除界面,会调用node的Destroy()方法。注意 单个json界面内的layer值应小于1000
生命周期函数
生命周期函数会被自动在以下情况下调用,重写函数可以完成一些逻辑。函数作用CreateUI创建成功时调用
OnActiveUI重新回到栈顶时调用
OnDeactive栈顶UI有其他UI入栈时调用
DestroyUI销毁时调用
下面是一些示例:xxxxxxxxxx
def Create(self):print "================ fpsui create "clientApi.HideHudGUI(False)comp = clientApi.CreateComponent(clientApi.GetLevelId(), "Minecraft", "operation")comp.all = TrueclientApi.NeedsUpdate(comp)def OnActive(self):print "================ fpsui OnActive "clientApi.HideHudGUI(False)comp = clientApi.CreateComponent(clientApi.GetLevelId(), "Minecraft", "operation")comp.all = TrueclientApi.NeedsUpdate(comp)def OnDeactive(self):print "================ fpsui OnDeactive "clientApi.HideHudGUI(True)comp = clientApi.CreateComponent(clientApi.GetLevelId(), "Minecraft", "operation")comp.all = FalseclientApi.NeedsUpdate(comp)def Destroy(self):print "================ fpsui Destroy "clientApi.HideHudGUI(False)comp = clientApi.CreateComponent(clientApi.GetLevelId(), "Minecraft", "operation")comp.all = TrueclientApi.NeedsUpdate(comp)