【HZ/ICMod开发教程】2 - UI PART2
注意:本教程仅为简单介绍UI的窗口,不会涉及详细的函数。如果想要了解更多请阅读官方文档。
在上期教程中我们简要介绍了ICMod的UI构成与基本控件,接下来将详细介绍控件用法以及如何组成一个“精美”的界面。
StandardWindow
在此纠正上一篇专栏的严重错误:StandartWindow在现在版本的InnerCore中已经弃用,使用StandardWindow替代。
StandardWindow
是一个由三部分组成的WindowGroup
,分别为main
, inventory
和header
,对应关系如下图:

在此使用工业的火力发电机界面作为示例:

这是一个简单的界面,但基本上包含了常用控件,让我们简要分析一下其代码:
//源码使用TypeScript编写且分不同部分在多个文件中,为方便讲解取主要代码并使用JavaScript重写
const GUI_SCALE = 3.2; //声明GUI_SCALE常量,以便统一模组内各界面
new UI.StandardWindow({
standard: {
header: {text: {text: Translation.translate("Generator")}}, //此处使用Translate类函数以实现多语言,详见官方文档
inventory: {standard: true},
background: {standard: true}
},
drawing: [//两个scale元素对应的当其为空时的贴图
{type: "bitmap", x: 530, y: 144, bitmap: "energy_bar_background", scale: GUI_SCALE},
{type: "bitmap", x: 450, y: 150, bitmap: "fire_background", scale: GUI_SCALE},
],
elements: {
"energyScale": {type: "scale", x: 530 + GUI_SCALE * 4, y: 144, direction: 0, value: 0.5, bitmap: "energy_bar_scale", scale: GUI_SCALE}, //注意此处x的值比对应的bitmap加了GUI_SCALE*4,这是因为它的贴图两端各少了4个像素,以便完整显示进度
"burningScale": {type: "scale", x: 450, y: 150, direction: 1, value: 0.5, bitmap: "fire_scale", scale: GUI_SCALE},
"slotEnergy": {type: "slot", x: 441, y: 75},
"slotFuel": {type: "slot", x: 441, y: 212},
"textInfo1": {type: "text", x: 642, y: 142, width: 300, height: 30, text: "0/"},
"textInfo2": {type: "text", x: 642, y: 172, width: 300, height: 30, text: "10000"}
}
});
可以看出在StandardWindow中使用的是绝对坐标,而且可与贴图像素对应,因此只需要耐心的慢慢调整就能够得到一个不错的界面。
与方块实体互动
此部分不是本教程的主要内容,因此将简要带过。
在IC中,注册方块实体的函数为TileEntity.registerPrototype(blockID: number, customPrototype: TileEntityPrototype): void
,参数只有两个:方块ID
和方块实体原型
。
方块实体原型是一个JS对象,包含方块实体的数据和事件。要将创建的StandardWindow对象与方块实体绑定则需要在方块实体原型中设置getGuiScreen
函数并返回界面对象。这样IC会自动为该界面对象创建相应的容器对象,你可以在方块实体原型的函数中使用this.container
来获得容器对象并使用其提供的方法与界面交互。
更进一步
StandardWindow的可玩性远不止如此,你可以试着除去StandardWindow的默认控件,并从0开始自定义界面,或者在方块实体原型的getGuiScreen
方法中编写逻辑以使在不同的情况下打开不同的界面。
示例为我去年3月份初次尝试UI时所写的会根据MC设置的UI档案切换StandardWindow的方块实体(PS:图二的界面标题有偏移Bug,在之后的版本中已被修复)。整个界面从开始构思到完成大概用了5天时间(大部分时间用在编写库上,但如今得益于WindowGroup,可以更快地完成该任务。


Window
Window
是最基本的窗口,相较于StandardWindow,Window更为灵活,适合用作弹窗或者HUD。
在创建一个Window对象的时候,可以像StandardWindow那样传入一个包含Drawing和Elements等的对象。但能够突出Window特色的是location参数,你可以自定义Window的大小,在屏幕上的位置,内边距和可滑动窗口大小(PS:Window内的unit
为此Window宽度的千分之一)。
值得一提,内边距padding
的会覆盖x|y|width|height
的效果。
举个简单的空白窗口界面的例子:
//定义一些常量
const GUI_SCALE = 5;
const WIDTH = 1000;
const HEIGHT = UI.getScreenHeight();
//创建窗口
let testWindow = new UI.Window({
location: { //此处运算是为方便居中
x: (WIDTH - 300) / 2,
y: (HEIGHT - 225) / 2,
width: 300,
height: 225
},
drawing: [ //贴图是从拆原版包扒的XD
{type: "background", color: android.graphics.Color.TRANSPARENT},
{type: "frame", bitmap: "background_panel", width: 1000, height: 750, scale: GUI_SCALE}
],
elements: {
"closeButton": {type: "closeButton", x: 904, y: 26, bitmap: "close_button_default", bitmap2: "close_button_pressed", scale: GUI_SCALE}
}
});
//一些常规设置
testWindow.setCloseOnBackPressed(true);
testWindow.setBlockingBackground(true);
效果图:

TabbedWindow
TabbedWindow
可用于创建标签式窗口,如原版的玩家背包一样,具有多个可切换的标签页。
TabbedWindow和StandardWindow一样,都继承自WindowGroup。在创建TabbedWindow对象时,所传入的参数与Window相同,与之不同的是TabbedWindow多了一些函数。
使用new UI.TabbedWindow()
创建一个空白的TabbedWindow,效果如下(PS:部分异形屏可能会被遮挡部分边界):

简单地说明一下这个窗口,左上角的关闭按钮是一个索引值为0的FakeTab
(PS:FakeTab指没有对应标签页的标签),标签页的上限为12个,即索引值的范围为0-11
(左侧为0-5
,右侧为6-11
)。
要添加一个标签页可以使用setTab(index: number, tabOverlay: ElementSet, tabContent: WindowContent, isAlwaysSelected?: boolean): void
函数,index
即标签页的索引值,tabOverlay
是标签的元素集(如关闭按钮),tabContent即为标签页展示的窗口内容,格式与之前介绍的基本无异。
因为setTab函数并不返回创建的Window对象,所以你需要用到getWindowForTab(index: number): Window
来函数获取对应索引值的Window实例化对象,以完成对其的动态修改。
WindowGroup
WindowGroup
是StandardWindow和TabbedWindow的父类,其方法基本上都可以在StandardWindow和TabbedWindow中使用。
WindowGroup诞生的目的是为了能够将复杂的界面模块化,以便提高代码的复用率并降低调试难度,使开发效率提高,最为典型的例子就是IC中的工作台界面(PS:此处不谈其交互逻辑,仅谈其界面)。

该界面由三部分组成,分别为Main
·,Slots
和Grid
,对应下图中的青色,粉色和黄色部分:

阅读workbench.js
中工作台界面部分的代码,可以看出该三部分都是先定义WindowContent的JSON描述,然后一一创建Window对象,并使用addWindowInstance(name: string, window: Window): void
函数将Window对象添加到WindowGroup对象并声明其对应的名称ID。你也可以直接使用addWindow(name: string, content: WindowContent): Window
函数向WindowGroup添加窗口。

至此,关于UI界面的内容就基本上介绍完毕,当然全部内容不止这些,你需要认真地查阅官方文档以及去学习他人的优秀作品。 另外,虽然本教程对于UI与容器的互动只是简单地带过,但此部分内容是相当重要的,应当注重学习(PS:谁让你们当时评论不提容器和方块实体呢XD)。