本文介绍Don't Starve 图形用户界面(Graphical User Interface,简称GUI)编程的基本概念,并给出简单的操作实例。
基本概念
在Don't Starve中,图形界面的核心由两个大类组成:Widget和Screen。Widget是用于界面上的小组件的,比如说一个物品栏,物品栏上的一个单元,都是一个简单的widget。Screen则是一整个界面,比如说选项界面,小地图界面等等。一个Screen里可以包含多个widget,一个widget也可以内含许多widget。在游戏中,widget和screen并没有明确的分界线,这里说的概念也仅仅是一种逻辑上的区分而已。由于在GUI编程中,Widget是Screen开发的基础,而且实际开发中使用的频率远远高于Screen,所以这里主要讲解Widget,而把Screen作为拓展内容。
Widget和Screen都只是一个基本类,要实现诸如按钮,文字输入等功能,则需要进行扩展,也就是继承Widget或Screen类,在此基础上更进一步增加新的东西。官方已经做好了一些常用功能的扩展,包括按钮,文字,图片,动画等等。在联机版里,为了进一步节约开发时间,官方还做了一些常用模板。这些模板是为固定情景而设计好的完整的Widget,可以简单地通过一两句代码调用,而无需再自行编写复杂的Widget。
Child
在图形编程中,常常会用到一个函数AddChild。这个函数会将传递进来的对象设置成执行这个函数的对象(我们称为Parent)的一个Child。从而在Parent执行一些操作时,会让Child同步执行。
比如说,当Parent的坐标变化时,Child的坐标也会跟着变化。再比如说,Child的原点会被设置成Parent的坐标,从而使得Child的坐标变成相对坐标,在调整时无需关心Child的屏幕坐标,只需要关心它相对于Parent的位置偏移就行了。这就使得Parent和Child变成一个紧密相连的整体,大大简化了相关的操作。
Hello Widget
大多数编程的学习,都是从最简单的Hello World开始,我们这里就从编写一个可以在游戏内顶部居中显示"Hello Klei"的Widget开始。
这需要做两件事:
- 编写一个Widget
- 把这个Widget加载到游戏里去
创建Widget
创建Widget是十分容易的,你只需要为你的widget想一个名字,比如Hello,然后在mod根目录/scripts/widgets文件夹下创建一个lua文件hello.lua。然后写一个类,继承Widget类或它的一个子类即可。
一般来说,推荐直接继承Widget,对于想要使用的特别功能,使用组合的方式来实现。这是因为,在MOD制作者编写的Widget通常是复合型的,比如可能同时含有按钮和文本,这时候,无论是继承Button类还是Text类,在逻辑上都是不合适的。正确的做法是,继承Widget类,再在成员变量里按需要添加Button和Text。
首先,创建一个mod项目,然后在mod根目录/scripts/widgets文件夹下创建一个lua文件hello.lua,然后,编写代码如下:
-- 首先,在文件的头部写上需要加载的Widget类
local Widget = require "widgets/widget" --Widget,所有widget的祖先类
local Text = require "widgets/text" --Text类,文本处理
local Hello = Class(Widget, function(self) -- 这里定义了一个Class,第一个参数是父类,第二个参数是构造函数,函数的参数第一个固定为self,后面的参数可以不写,也可以自定义。
Widget._ctor(self, "Hello") --这一句必须写在构造函数的第一行,否则会报错。
--这表明调用父类的构造函数(此处是Widget,如果继承Text,则应该写Text._ctor),第一个参数是固定的self,后面的参数同这个父类的构造函数的参数,此处写的是Widget的名字。
--
self.text = self:AddChild(Text(BODYTEXTFONT, 30,"Hello Klei")) --添加一个文本变量,接收Text实例。
end)
return Hello
如此就完成了Hello Widget的编写。
加载Widget
Widget实质上是一个小部件,它需要依附于screen才能使用。游戏里通过FrontEnd来调度不同的screen,从而显示出不同的Widget供玩家查看和操作。
不过,一般来说,用于MOD的widget,多数不需要直接依附于screen,而只需要依附于screen下的一个widget就行了。
以最常见的,为游戏中的操作界面添加widget为例,screen是HUD,但我们不需要让自己编写的widget直接依附在HUD上,只需要依附在controls这个widget上就行了。controls是一个大型的综合性widget,玩家操作界面的物品栏,制作栏,状态栏等等,都是由这个widget统一进行管理的。
下面就来把Hello Widget添加到这个controls里。
在modmain.lua里添加如下内容:
local hello = GLOBAL.require("widgets/hello") --加载hello类
local function addHelloWidget(self)
self.hello = self:AddChild(hello())-- 为controls添加hello widget。
self.hello:SetHAnchor(0) -- 设置原点x坐标位置,0、1、2分别对应屏幕中、左、右
self.hello:SetVAnchor(1) -- 设置原点y坐标位置,0、1、2分别对应屏幕中、上、下
self.hello:SetPosition(70,-50,0) -- 设置hello widget相对原点的偏移量,70,-50表明向右70,向下50,第三个参数无意义。
end
AddClassPostConstruct("widgets/controls", addHelloWidget) -- 这个函数是官方的MOD API,用于修改游戏中的类的构造函数。第一个参数是类的文件路径,根目录为scripts。第二个自定义的修改函数,第一个参数固定为self,指代要修改的类。
成果展示
开启MOD,随便选个角色进入游戏,你会在屏幕上方看到Hello Klei的字样
常用Widget
官方已经编写好了大量可用的Widget,包含了大量基本功能,联机版更有许多模板可以直接使用。我们进行图形界面编程时,通常不需要再费时费力地从头编写一个全新的Widget,大多数时候只需要使用官方提供的Widget进行组合,甚至直接使用模板就足够了。
这里只介绍一些常用的Widget及其子类,说明基本功能和使用场景。在后续教程里会对常用的Widget和模板进行详细介绍,并提供可参考的实例。
Text/文本
文本类Text主要用于文本呈现和处理。
基类为Text,有一个扩展子类:TextEdit
核心函数
构造函数
: 文本类的主要功能就是文本呈现,而类的构造函数可以直接定义文本该以怎样的形式呈现(字体,大小,内容,颜色)
Text/文本
Text只提供文本呈现功能,能够设置文本的字体,大小,内容和颜色。Text的使用范围十分广泛,任何需要呈现文字的地方都需要用到Text。
TextEdit/文本编辑
TextEdit在文本呈现的基础上,额外提供了文本编辑功能,主要用于各种输入框,如聊天输入框,控制台输入框等等。
它还有一个很特殊的扩展子类TextEditLinked,用于礼品兑换码的输入框,一般不使用。
FollowText/跟随文本
跟随文本类FollowText,和Text很像,但不是Text的子类。与Text的区别在于它的位置是动态变化的。常见应用于显示动作的名字,比如,当你手持斧头时,把光标移动到树上,会出现两个字——砍树。
Image/图片
图片类Image,主要用于图片呈现,没有子类。
核心函数
构造函数
: 图片类和文本类相似,主要功能就是图片呈现,可以定义图片的atlas文件和tex名。
Button/按钮
按钮类Button主要提供一个点击操作。通过设定点击操作触发的函数,让玩家可以通过执行某些功能。
类名 | 描述 |
---|---|
ImageButton | 图片按钮,最常用 |
TextButton | 文字按钮,偶尔会用 |
ListCursor | 列表游标,和拖动条联合使用。常用场景为服务器列表 |
AnimButton | 动画按钮,极少使用 |
UIAnimButton | 界面动画按钮,极少使用 |
基类为Button,子类如下表:
类名 | 描述 |
---|---|
ImageButton | 图片按钮,最常用 |
TextButton | 文字按钮,偶尔会用 |
ListCursor | 列表游标,和拖动条联合使用。常用场景为服务器列表 |
AnimButton | 动画按钮,极少使用 |
UIAnimButton | 界面动画按钮,极少使用 |
基类Button仅仅定义了点击触发函数的功能,但没有定义它该以何种形式来呈现在玩家操作界面上。在实际使用中,通常不直接使用基类Button,而是根据情况使用它的各种子类,其中最为常用的是ImageButton。
核心函数
按钮的主要功能就是在点击后触发函数,因此设置点击触发函数的函数就是核心函数。
SetOnDown( fn )
: 设置按下按钮弹起前触发的函数
SetOnClick(fn)
: 设置按下按钮弹起后触发的函数
以上两个设置函数里设置的fn,是没有参数的。
ImageButton/图片按钮
大多数按钮都属于图片按钮,可以直接在构造图片按钮实例时传入atlas(该按钮图片的统一管理xml文件), normal(一般状态下的图片,下面的类似), focus(聚焦状态), disabled(不可用状态), down(按下状态), selected(选中状态), image_scale(图片缩放大小), image_offset(图片偏移量)等一系列参数。
对于缺失的参数,会使用缺省值代替。所以在使用时,不写任何参数也是可以的。
Badge/徽章
徽章类Badge,主要用于呈现一个圆形物体的动画,常见应用是各种指示器:饥饿度、精神度、血量、木头值(吴迪),通过设置动画播放的百分比来指示某个数值的多少。
特别提醒:Badge使用的动画通常是逆过来的,也就是100%的状态在开端,0%的状态在结尾。这是为了保证状态为100%时一定能播放出相应的动画关键帧。(动画的0帧一定能播放出来,但最后一帧则不一定)。
类名 | 描述 |
---|---|
HungerBadge | 饥饿指示器 |
SanityBadge | 精神指示器 |
HealthBadge | 血量指示器 |
BeaverBadge | 木头值指示器 |
基类为Badge,子类如下表:
类名 | 描述 |
---|---|
HungerBadge | 饥饿指示器 |
SanityBadge | 精神指示器 |
HealthBadge | 血量指示器 |
BeaverBadge | 木头值指示器 |
核心函数
构造函数
: animname, states分别指明要使用的动画build/bank名(build和bank名必须一致)和anim名。通常的使用方式是,拓展成一个新的子类,在父类构造函数中填写animname和states,具体可以参考已有的官方子类。
SetPercent(val, max)
: 设置动画播放的百分比。