本文介紹Don't Starve 圖形用戶界面(Graphical User Interface,簡稱GUI)編程的基本概念,并給出簡單的操作實例。
基本概念
在Don't Starve中,圖形界面的核心由兩個大類組成:Widget和Screen。Widget是用于界面上的小組件的,比如說一個物品欄,物品欄上的一個單元,都是一個簡單的widget。Screen則是一整個界面,比如說選項界面,小地圖界面等等。一個Screen里可以包含多個widget,一個widget也可以內(nèi)含許多widget。在游戲中,widget和screen并沒有明確的分界線,這里說的概念也僅僅是一種邏輯上的區(qū)分而已。由于在GUI編程中,Widget是Screen開發(fā)的基礎(chǔ),而且實際開發(fā)中使用的頻率遠(yuǎn)遠(yuǎn)高于Screen,所以這里主要講解Widget,而把Screen作為拓展內(nèi)容。
Widget和Screen都只是一個基本類,要實現(xiàn)諸如按鈕,文字輸入等功能,則需要進(jìn)行擴展,也就是繼承Widget或Screen類,在此基礎(chǔ)上更進(jìn)一步增加新的東西。官方已經(jīng)做好了一些常用功能的擴展,包括按鈕,文字,圖片,動畫等等。在聯(lián)機版里,為了進(jìn)一步節(jié)約開發(fā)時間,官方還做了一些常用模板。這些模板是為固定情景而設(shè)計好的完整的Widget,可以簡單地通過一兩句代碼調(diào)用,而無需再自行編寫復(fù)雜的Widget。
Child
在圖形編程中,常常會用到一個函數(shù)AddChild。這個函數(shù)會將傳遞進(jìn)來的對象設(shè)置成執(zhí)行這個函數(shù)的對象(我們稱為Parent)的一個Child。從而在Parent執(zhí)行一些操作時,會讓Child同步執(zhí)行。
比如說,當(dāng)Parent的坐標(biāo)變化時,Child的坐標(biāo)也會跟著變化。再比如說,Child的原點會被設(shè)置成Parent的坐標(biāo),從而使得Child的坐標(biāo)變成相對坐標(biāo),在調(diào)整時無需關(guān)心Child的屏幕坐標(biāo),只需要關(guān)心它相對于Parent的位置偏移就行了。這就使得Parent和Child變成一個緊密相連的整體,大大簡化了相關(guān)的操作。
Hello Widget
大多數(shù)編程的學(xué)習(xí),都是從最簡單的Hello World開始,我們這里就從編寫一個可以在游戲內(nèi)頂部居中顯示"Hello Klei"的Widget開始。
這需要做兩件事:
- 編寫一個Widget
- 把這個Widget加載到游戲里去
創(chuàng)建Widget
創(chuàng)建Widget是十分容易的,你只需要為你的widget想一個名字,比如Hello,然后在mod根目錄/scripts/widgets文件夾下創(chuàng)建一個lua文件hello.lua。然后寫一個類,繼承Widget類或它的一個子類即可。
一般來說,推薦直接繼承Widget,對于想要使用的特別功能,使用組合的方式來實現(xiàn)。這是因為,在MOD制作者編寫的Widget通常是復(fù)合型的,比如可能同時含有按鈕和文本,這時候,無論是繼承Button類還是Text類,在邏輯上都是不合適的。正確的做法是,繼承Widget類,再在成員變量里按需要添加Button和Text。
首先,創(chuàng)建一個mod項目,然后在mod根目錄/scripts/widgets文件夾下創(chuàng)建一個lua文件hello.lua,然后,編寫代碼如下:
-- 首先,在文件的頭部寫上需要加載的Widget類
local Widget = require "widgets/widget" --Widget,所有widget的祖先類
local Text = require "widgets/text" --Text類,文本處理
local Hello = Class(Widget, function(self) -- 這里定義了一個Class,第一個參數(shù)是父類,第二個參數(shù)是構(gòu)造函數(shù),函數(shù)的參數(shù)第一個固定為self,后面的參數(shù)可以不寫,也可以自定義。
Widget._ctor(self, "Hello") --這一句必須寫在構(gòu)造函數(shù)的第一行,否則會報錯。
--這表明調(diào)用父類的構(gòu)造函數(shù)(此處是Widget,如果繼承Text,則應(yīng)該寫Text._ctor),第一個參數(shù)是固定的self,后面的參數(shù)同這個父類的構(gòu)造函數(shù)的參數(shù),此處寫的是Widget的名字。
--
self.text = self:AddChild(Text(BODYTEXTFONT, 30,"Hello Klei")) --添加一個文本變量,接收Text實例。
end)
return Hello
如此就完成了Hello Widget的編寫。
加載Widget
Widget實質(zhì)上是一個小部件,它需要依附于screen才能使用。游戲里通過FrontEnd來調(diào)度不同的screen,從而顯示出不同的Widget供玩家查看和操作。
不過,一般來說,用于MOD的widget,多數(shù)不需要直接依附于screen,而只需要依附于screen下的一個widget就行了。
以最常見的,為游戲中的操作界面添加widget為例,screen是HUD,但我們不需要讓自己編寫的widget直接依附在HUD上,只需要依附在controls這個widget上就行了。controls是一個大型的綜合性widget,玩家操作界面的物品欄,制作欄,狀態(tài)欄等等,都是由這個widget統(tǒng)一進(jìn)行管理的。
下面就來把Hello Widget添加到這個controls里。
在modmain.lua里添加如下內(nèi)容:
local hello = GLOBAL.require("widgets/hello") --加載hello類
local function addHelloWidget(self)
self.hello = self:AddChild(hello())-- 為controls添加hello widget。
self.hello:SetHAnchor(0) -- 設(shè)置原點x坐標(biāo)位置,0、1、2分別對應(yīng)屏幕中、左、右
self.hello:SetVAnchor(1) -- 設(shè)置原點y坐標(biāo)位置,0、1、2分別對應(yīng)屏幕中、上、下
self.hello:SetPosition(70,-50,0) -- 設(shè)置hello widget相對原點的偏移量,70,-50表明向右70,向下50,第三個參數(shù)無意義。
end
AddClassPostConstruct("widgets/controls", addHelloWidget) -- 這個函數(shù)是官方的MOD API,用于修改游戲中的類的構(gòu)造函數(shù)。第一個參數(shù)是類的文件路徑,根目錄為scripts。第二個自定義的修改函數(shù),第一個參數(shù)固定為self,指代要修改的類。
成果展示
開啟MOD,隨便選個角色進(jìn)入游戲,你會在屏幕上方看到Hello Klei的字樣

常用Widget
官方已經(jīng)編寫好了大量可用的Widget,包含了大量基本功能,聯(lián)機版更有許多模板可以直接使用。我們進(jìn)行圖形界面編程時,通常不需要再費時費力地從頭編寫一個全新的Widget,大多數(shù)時候只需要使用官方提供的Widget進(jìn)行組合,甚至直接使用模板就足夠了。
這里只介紹一些常用的Widget及其子類,說明基本功能和使用場景。在后續(xù)教程里會對常用的Widget和模板進(jìn)行詳細(xì)介紹,并提供可參考的實例。
Text/文本
文本類Text主要用于文本呈現(xiàn)和處理。
基類為Text,有一個擴展子類:TextEdit
核心函數(shù)
構(gòu)造函數(shù)
: 文本類的主要功能就是文本呈現(xiàn),而類的構(gòu)造函數(shù)可以直接定義文本該以怎樣的形式呈現(xiàn)(字體,大小,內(nèi)容,顏色)
Text/文本
Text只提供文本呈現(xiàn)功能,能夠設(shè)置文本的字體,大小,內(nèi)容和顏色。Text的使用范圍十分廣泛,任何需要呈現(xiàn)文字的地方都需要用到Text。
TextEdit/文本編輯
TextEdit在文本呈現(xiàn)的基礎(chǔ)上,額外提供了文本編輯功能,主要用于各種輸入框,如聊天輸入框,控制臺輸入框等等。
它還有一個很特殊的擴展子類TextEditLinked,用于禮品兌換碼的輸入框,一般不使用。
FollowText/跟隨文本
跟隨文本類FollowText,和Text很像,但不是Text的子類。與Text的區(qū)別在于它的位置是動態(tài)變化的。常見應(yīng)用于顯示動作的名字,比如,當(dāng)你手持斧頭時,把光標(biāo)移動到樹上,會出現(xiàn)兩個字——砍樹。
Image/圖片
圖片類Image,主要用于圖片呈現(xiàn),沒有子類。
核心函數(shù)
構(gòu)造函數(shù)
: 圖片類和文本類相似,主要功能就是圖片呈現(xiàn),可以定義圖片的atlas文件和tex名。
Button/按鈕
按鈕類Button主要提供一個點擊操作。通過設(shè)定點擊操作觸發(fā)的函數(shù),讓玩家可以通過執(zhí)行某些功能。
| 類名 | 描述 |
|---|---|
| ImageButton | 圖片按鈕,最常用 |
| TextButton | 文字按鈕,偶爾會用 |
| ListCursor | 列表游標(biāo),和拖動條聯(lián)合使用。常用場景為服務(wù)器列表 |
| AnimButton | 動畫按鈕,極少使用 |
| UIAnimButton | 界面動畫按鈕,極少使用 |
基類為Button,子類如下表:
| 類名 | 描述 |
|---|---|
| ImageButton | 圖片按鈕,最常用 |
| TextButton | 文字按鈕,偶爾會用 |
| ListCursor | 列表游標(biāo),和拖動條聯(lián)合使用。常用場景為服務(wù)器列表 |
| AnimButton | 動畫按鈕,極少使用 |
| UIAnimButton | 界面動畫按鈕,極少使用 |
基類Button僅僅定義了點擊觸發(fā)函數(shù)的功能,但沒有定義它該以何種形式來呈現(xiàn)在玩家操作界面上。在實際使用中,通常不直接使用基類Button,而是根據(jù)情況使用它的各種子類,其中最為常用的是ImageButton。
核心函數(shù)
按鈕的主要功能就是在點擊后觸發(fā)函數(shù),因此設(shè)置點擊觸發(fā)函數(shù)的函數(shù)就是核心函數(shù)。
SetOnDown( fn )
: 設(shè)置按下按鈕彈起前觸發(fā)的函數(shù)
SetOnClick(fn)
: 設(shè)置按下按鈕彈起后觸發(fā)的函數(shù)
以上兩個設(shè)置函數(shù)里設(shè)置的fn,是沒有參數(shù)的。
ImageButton/圖片按鈕
大多數(shù)按鈕都屬于圖片按鈕,可以直接在構(gòu)造圖片按鈕實例時傳入atlas(該按鈕圖片的統(tǒng)一管理xml文件), normal(一般狀態(tài)下的圖片,下面的類似), focus(聚焦?fàn)顟B(tài)), disabled(不可用狀態(tài)), down(按下狀態(tài)), selected(選中狀態(tài)), image_scale(圖片縮放大?。? image_offset(圖片偏移量)等一系列參數(shù)。
對于缺失的參數(shù),會使用缺省值代替。所以在使用時,不寫任何參數(shù)也是可以的。
Badge/徽章
徽章類Badge,主要用于呈現(xiàn)一個圓形物體的動畫,常見應(yīng)用是各種指示器:饑餓度、精神度、血量、木頭值(吳迪),通過設(shè)置動畫播放的百分比來指示某個數(shù)值的多少。
特別提醒:Badge使用的動畫通常是逆過來的,也就是100%的狀態(tài)在開端,0%的狀態(tài)在結(jié)尾。這是為了保證狀態(tài)為100%時一定能播放出相應(yīng)的動畫關(guān)鍵幀。(動畫的0幀一定能播放出來,但最后一幀則不一定)。
| 類名 | 描述 |
|---|---|
| HungerBadge | 饑餓指示器 |
| SanityBadge | 精神指示器 |
| HealthBadge | 血量指示器 |
| BeaverBadge | 木頭值指示器 |
基類為Badge,子類如下表:
| 類名 | 描述 |
|---|---|
| HungerBadge | 饑餓指示器 |
| SanityBadge | 精神指示器 |
| HealthBadge | 血量指示器 |
| BeaverBadge | 木頭值指示器 |
核心函數(shù)
構(gòu)造函數(shù)
: animname, states分別指明要使用的動畫build/bank名(build和bank名必須一致)和anim名。通常的使用方式是,拓展成一個新的子類,在父類構(gòu)造函數(shù)中填寫animname和states,具體可以參考已有的官方子類。
SetPercent(val, max)
: 設(shè)置動畫播放的百分比。