第四章 Prefab-基礎知識


這一章講解Prefab。Prefab是饑荒世界構成的基礎,也是Mod技術的基本內容。

Prefab,中文譯名叫預制物,也可以廣義地稱之為物體。
在饑荒的世界中,Prefab是最基礎的元素,除了操作面板和地圖外,所有的一切,包括食物、人物、動物、植物、水池、礦石乃至于特效等等,都是Prefab。可以說,了解Prefab,是制作饑荒MOD的基礎。

本章將講解Prefab的相關基礎知識,下一章將講解如何構建不同種類的Prefab。如果你急于知道如何做一件衣服,一頂帽子,或者一件法杖,那么你可以先跳過本章,看下一章。但我建議你認真學習Prefab的基礎知識。這些基礎知識是構建的根基,只有掌握它們,你才有能力發揮你的創造力。

本章內容較多,先了解知識結構有助于更好地學習。

知識結構如下圖,本章只講述基礎知識的部分

Prefab基礎知識結構.png

基本概念

Prefab,可以說是饑荒世界的原子,一棵樹,一塊卵石,一口鍋等等……凡是在世界地圖中能夠參與互動的,全部都是Prefab。

想要構建一個Prefab對象,可以用Prefab類的構造函數來表示:Prefab("common/inventory/lotus_umbrella", fn, assets)
一個Prefab由三部分組成,對應Prefab類構造函數的三個參數,分別是Prefab名,描述函數和加載資源表。

  • Prefab名:用于向系統注冊,從而使得系統能夠精確地定位到某個Prefab進行操作,比如生成一棵草。在Prefab類構造函數中,只會識別最后一個/后面的字符。比如上面的例子,系統會認為Prefab名為lotus_umbrella
  • 描述函數:用于描述Prefab的內涵,比如說它的外表是什么,有什么功能等等,應該傳入一個函數。
  • 加載資源表:用于向系統說明,為了正確地呈現這個Prefab,需要加載那些動畫、圖片、聲音文件,應該傳入一張表。

Prefab分解

--------------------------------------- 加載資源表 -----------------------------------
local assets =
{
    Asset("ANIM", "anim/lotus_umbrella.zip"),
    Asset("ANIM", "anim/swap_lotus_umbrella.zip"),
    Asset("ATLAS", "images/inventoryimages/lotus_umbrella.xml"),
}
--------------------------------------- end 加載資源表  -----------------------------------


--------------------------------------- 描述函數 -----------------------------------
... 一些定義在外部的函數

local function fn() -- 描述函數
    local inst = CreateEntity() -- 創建實體
    ... 對inst添加各種各樣的組件,并對每個組件進行一些設置
    return inst
end
--------------------------------------- end 描述函數  -----------------------------------



return Prefab("common/inventory/lotus_umbrella", fn, assets) -- 第一個參數就是Prefab名,系統只會識別最后一個斜杠后面的名字,fn代表描述函數,assets代表加載資源表

Prefab名是很容易理解的,直接寫一個自己喜歡的名字就可以了,重點在于了解如何描述加載資源表和描述函數。

加載資源表

加載資源表本質上就是一個Lua table,其中的元素都是Asset對象。如果不明白這句話也沒關系,使用起來是非常簡單的。你只需要弄清楚當前的Prafab需要什么樣的動畫、圖片、聲音資源,以及它們的存放位置,然后用Asset逐行描述出來就行了。

一張典型的加載資源表

local assets =
{
    Asset("ANIM", "anim/lotus_umbrella.zip"),
    Asset("ANIM", "anim/swap_lotus_umbrella.zip"),
    Asset("ATLAS", "images/inventoryimages/lotus_umbrella.xml"),
}

Asset第一個參數是資源類型,第二個參數則是資源文件的路徑。
各類型資源的對應名稱如下,注意要大寫

  • 動畫:類型名為ANIM
  • 圖片:只需要指明xml文檔的路徑就行,tex文件不需要給出(xml文檔會指明tex文件的所在)。類型名為ATLAS
  • 聲音:fev后綴的聲音集合文件,對應類型"SOUNDPACKAGE",fsb后綴的聲音實體文件,對應類型為"SOUND"

描述函數

描述函數是Prefab中最重要的部分,這部分的編程邏輯看起來很復雜,但本質也是很簡單的,在描述函數里,我們實質上只是在做三件事:

  1. 創建實體 local inst = CreateEntity()
  2. 為實體(entity)添加各種組件,并設定組件的初始狀態
  3. 返回實體 return inst

可以用前一節的部分代碼表述如下

--------------------------------------- 描述函數 -----------------------------------
... 一些定義在外部的函數
local function fn() -- 描述函數
    local inst = CreateEntity() -- 創建實體
    ... 對inst添加各種各樣的組件,并對每個組件進行一些設置
    return inst
end
--------------------------------------- end 描述函數  -----------------------------------

創建實體的方法就是local inst = CreateEntity(),這里變量名inst就是instance的簡寫,是一種約定的寫法,建議沿用。
最簡單的Prefab,就是不添加任何組件的Prefab,創建實體后直接返回該實體即可。只是不添加任何組件的話這個Prefab就沒有任何功能可言,這不是我們想要的。由此我們了解到,Prefab的本質就是一個可以填充各種內容的實體。為了更便于理解,下文中當實體和Prefab之間沒有歧義的時候,會選擇用實體這個稱呼。

組成結構

創建實體實體和返回實體都是非常簡單的,所有的Prefab在這一點上都是一致的,重要的是了解如何向實體添加組件,以及應該添加哪些組件,如何設置組件的初始狀態。不同的組件和組件初始狀態,造就了饑荒世界豐富多彩的Prefab。

在游戲系統中,有很少一部分組件是需要和游戲引擎進行直接交互的,要添加這類組件,必須使用inst.entity:AddXXX()這樣的代碼。這些組件是用C++進行編寫的,我們無法了解其內部細節,更無法進行修改,我稱之為Entity組件。我們只能了解Entity組件的各種方法,并用這些方法來設置組件的初始狀態。
除去少數Entity組件,更多的組件是用lua語言編寫的,要添加這些組件,代碼格式為inst:AddComponent("xxx")。我們還可以在官方的compoentns文件夾下的lua文件中直接看到相應源碼,了解相關的所有細節。這樣的組件,就直接沿用官方的稱呼,Component。
兩類組件只在添加方式上有些差異,使用的方法是相似的。下面將重點講解Entity組件的使用方法,而Component由于數量眾多,方法繁雜,會在后續單獨劃出一章來講解。

在聯機版中,常常需要考慮聯機的問題。如果你希望你的Prefab在生成之后,還能與其它電腦進行通信,就需要添加一段設置網絡代碼,鑒于大部分Prefab都需要在主客機之間進行通信交互,所以此片段幾乎是必定要添加的。本章屬于入門性質,不會過多探討主客機網絡通信的問題,僅僅給出描述函數更為詳細的代碼結構如下:

--------------------------------------- 描述函數 -----------------------------------
... 一些定義在外部的函數
local function fn() -- 描述函數
    local inst = CreateEntity() -- 創建實體
    -- 在網絡代碼往上的這部分代碼,會在所有主機和客戶端上都運行
    -- Entity組件,Tag和網絡變量
    -- 主客機通用Component(如用于處理說話的talker)
    -- 主客機通用自定義處理
    
    -------------- 網絡代碼 -----------------
    inst.entity:AddNetwork()
    inst.entity:SetPristine()
    if not TheWorld.ismastersim then
        return inst
    end
    -------------- END 網絡代碼 -------------
    -- 從這里往下的代碼,只會在主機上運行。
    -- 大多數Component
    -- sg和brain
    -- 主機端自定義處理
    
    
    return inst
end
--------------------------------------- end 描述函數  -----------------------------------

Entity組件

Entity組件的使用頻率很高,幾乎每個Prefab都會有,而Component則不一定會有。需要注意的是,Entity組件是在主客機端都會運行的,所以在描述函數里,初始化設置的部分必須寫在網絡代碼片段的上面。

Entity組件的數量不多,所以可以直接全部列出來:

  • Transform:變換組件,控制Prefab的位置、方向、縮放等等
  • AnimState:動畫組件,控制Prefab的材質(Build),動畫集合(Bank)和動畫播放(Animation)
  • Phiysics:物理組件,控制Prefab的物理行為,比如速度,碰撞類型等等。
  • Light:光照組件,添加該組件可使得Prefab成為一個光源。
  • Network:網絡組件,添加與否決定了一個Prefab在主機上生成時,是否會被客戶端“看”到。
  • MapEntity:地圖實體組件,使用該組件可以為Prefab在小地圖上創建一個圖標。
  • SoundEmitter:聲音組件,控制Prefab的聲音集合和播放

添加一個Entity組件的代碼是inst.entity:AddXXX(),其中XXX是組件名,比如添加Transform組件,可以寫成inst.entity:AddTransform()
使用一個Entity組件的某個方法的代碼是inst.XXX:YYY(),其中XXX是組件名,YYY是方法名。比如說設定實體的材質(Build)為lotus_umbrella,則可以寫成 inst.AnimState:SetBuild("lotus_umbrella")

下面就來逐一講解Entity組件的常用方法,本章只會講解一部分Entity組件。

注意:下文中的inst表示引用實體對象的引用,請自行根據上下文改變對應變量名。

Transform-變換

Transform的中文譯名為變換,主要控制實體的位置、方向和縮放大小。

  • 位置

饑荒的空間坐標系是右手坐標系,x,z為地面坐標,y為高度坐標。坐標軸的具體的方向是隨著視角的旋轉而變化的,但坐標系本身是保持相對不變的。
不理解上面的這段話?沒關系,你只要知道x,z是地面坐標,y是高度坐標就行了。
獲取位置
inst.Transform:GetWorldPosition()
會返回三個值,分別對應實體當前所在的世界坐標x,y,z
官方還提供了一個用lua代碼封裝好的更簡潔的用法
inst:GetPosition()
與上面的Transform的方法不同,這個用法不會直接返回x,y,z的坐標,而是把坐標封裝成一個Point對象(x,y,z)。如果你想要返回x,y,z坐標,可以寫成:
inst:GetPosition():Get()
設置位置
inst.Transform:SetPosition(x, y, z)
其中x,y,z為世界坐標

  • 方向

雖然饑荒的人物看起來只有4個方向,但在系統中是能描述出實體的360°準確方向的。
具體的數值區間為-180°到180°
獲取方向
inst.Transform:GetRotation()
返回實體的當前方向角度degree,區間范圍為-180°到180°
設置方向
inst.Transform:SetPosition(degree)
設置實體的方向角度,如果degree為大于180或者小于-180的數,會自動轉化到對應-180到180區間上的角度。

  • 縮放

縮放是按比例來算的,也有x,y,z三個值,所有的實體默認初始縮放的x,y,z值為1,1,1
x,y決定縮放比例,z和x,y的比例則決定實體在左右和上下兩個方向上的速度。
一般情況下,推薦設置x,y,z為相同的值。成比例地擴大或縮小。
獲取縮放
inst.Transform:GetScale()
會返回三個值,分別對應縮放比例x,y,z
設置縮放
inst.Transform:SetScale(x, y, z)
其中x,y,z為縮放比例

  • Face

所謂Face,是指同一個實體在面向不同角度時,播放同一個動畫,會顯示不同形態,比如人物靜止不動時,面向上下左右,會有不同的姿態。

人物不同方向的站立姿態

人站立_上
人站立_下
人站立_右
人站立_左

這一點在特效施放時很重要,如果Face設置不當,特效就可能在某個朝向上不能正確顯示。

inst.Transform:SetNoFaced() --設置無面,始終只有一個動畫形態
inst.Transform:SetTwoFaced() --2面,只有下、右
inst.Transform:SetFourFaced() --4面,上下左右
inst.Transform:SetSixFaced() --6面,上下左右+左下、右上
inst.Transform:SetEightFaced() --8面,上下左右+四個斜向

常用方法表

方法名 參數 返回值 描述
GetWorldPosition 坐標x,y,z 獲取實體的位置
SetPosition 坐標x,y,z 設置實體的位置
GetPredictionPosition 坐標x,y,z 用于客機預測位置,降低網絡延遲造成的影響
GetLocalPosition 坐標x,y,z 對Prefab來說和GetWorldPosition沒有區別。但實體不止可以表示Prefab,也可以表示UI組件。這個方法一般是用在UI組件上
GetRotation 角度degree 獲取實體的方向角度
SetPosition 角度degree 設置實體的方向角度
GetScale 縮放比例x,y,z 獲取實體的縮放比例
SetScale 縮放比例x,y,z 設置實體的縮放比例
SetNoFaced 設置無面
SetTwoFaced 設置2面
SetFourFaced 設置4面
SetSixFaced 設置6面
SetEightFaced 設置8面

AnimState

AnimState負責處理實體的動畫內容。要掌握如何使用AnimState,就先需要了解饑荒中的動畫概念。

最基本的概念是Animation,也就是動畫。當一個Prefab在世界地圖上被呈現出來的時候,就是在播放一段動畫。不動的動畫也算是動畫,是只有1幀的動畫。動畫播放結束后,不會自己憑空消失,如果沒有進行設置的話,就會在世界地圖上呈現出最后一幀的畫面。對應到Spriter項目,就是單個動畫。
每個動畫會由多個部分組成,我們可以通過代碼來局部改變某個部分。這樣的一個部分就叫Symbol。最典型的例子就是在裝備物品時,會改變人物的外觀,這就是通過覆蓋Symbol來實現的。
然后是Bank,就是一堆動畫的集合,對應到Spriter項目,就是多個動畫的上級,一般來說,Bank一旦設定好了就不會改變,但也有例外,比如騎牛。騎在牛上和在地上用的是兩套不同的Bank。
最后是Build,Build就是材質,對應Spriter項目文件的名字。材質就是動畫的外在表現。比如說兔子,有黃色和白色兩種,區別只在于它們用了不同的Build。

  • 設置
  • SetBuild:設置Build,例:inst.AnimState:SetBuild("samansha")
  • SetBank:設置Bank,例:inst.AnimState:SetBank("wilson")
  • SetLayer:設置層級,會影響到多個物體重疊時的動畫呈現(誰在最前面),例:inst.AnimState:SetLayer(LAYER_WORLD)
  • SetOrientation:設置朝向,在設置Prefab緊貼地面時會很有用,比如農場,池塘都是緊貼地面的。例:inst.AnimState:SetOrientation(ANIM_ORIENTATION.OnGround)
  • SetSortOrder:設置排序順序,在層級相同時有影響。例:inst.AnimState:SetSortOrder(3)
  • 動畫播放
  • PlayAnimation:播放動畫,例:inst.AnimState:PlayAnimation("idle")
  • PushAnimation:推送動畫到動畫播放隊列中,Prefab會按隊列中各動畫的先后推送順序依次播放,與PlayAnimation的區別是,Push會等待隊列動畫播放結束,而Play是直接打斷播放隊列,立刻播放設定的動畫。例:inst.AnimState:PushAnimation("idle")
  • 局部修改
  • OverrideSymbol:用其它動畫的某個Symbol來覆蓋當前Prefab的Symbol,其中,三個參數分別為要覆蓋的當前Prefab的Symbol名,覆蓋用的動畫文件名(無后綴),覆蓋用的Symbol名:inst.AnimState:OverrideSymbol("swap_object", "swap_lotus_umbrella", "swap_lotus_umbrella")
  • Show:展現Prefab的某個Symbol,例:inst.AnimState:Show("ARM_carry")
  • Hide:隱藏Prefab的某個Symbol,例:inst.AnimState:Hide("ARM_normal")

常用方法表

方法名 參數 返回值 描述
SetBuild Build名 設置Build
SetBank Bank名 設置Bank
SetLayer Layer值 設置Layer
SetOrientation Orientation值 設置Orientation
SetSortOrder Order值 設置SortOrder
SetFinalOffset Offset值 設置tFinalOffset
PlayAnimation Animation名,是否循環 播放動畫,動畫名為第一個參數,第二個參數決定是否循環播放,默認為否,一般可以不填
PushAnimation Animation名,是否循環 推送動畫到播放隊列中,動畫名為第一個參數,第二個參數決定是否循環播放,默認為否,一般可以不填
OverrideSymbol 要覆蓋的Symbol名,覆蓋用的Build名,覆蓋用的Symbol名 用新Symbol覆蓋原Symbol
Show Symbol名 展示某個Symbol
Hide Symbol名 隱藏某個Symbol

常見AnimState方法組合
AnimState常用于改變Prefab的動畫表現,但單個方法能改變的內容很少,一般都是組合使用,這里給出一些常用的片段。

  • Prefab初始化

凡是需要在游戲中能被看到的Prefab,都需要添加初始化代碼。
初始化代碼應該寫在描述函數里,且在網絡代碼片段之前,約定默認播放動畫為idle。
內容很簡單,就是設置Build,Bank和初始播放動畫。

    inst.AnimState:SetBank("bank名")
    inst.AnimState:SetBuild("build名")
    inst.AnimState:PlayAnimation("idle")
  • 將農場、池塘等Prefab平放在地圖上

如果你注意觀察,會發現農場、池塘的動畫表現和一般的Prefab不一樣,它們似乎是被平放在了地圖上,而不像一般的Prefab是呈二維狀態,立起來的。你當然可以畫一個平放的圖,不過,這事用代碼來做更容易也更精確。

    inst.AnimState:SetOrientation(ANIM_ORIENTATION.OnGround) -- 設置Prefab平放在地上
    inst.AnimState:SetLayer(LAYER_BACKGROUND) -- 設置圖層位置,會影響到重疊動畫的表現,貼在地上的圖層會位于一般Prefab圖層之下。比如人物會遮蓋農場的動畫。
    inst.AnimState:SetSortOrder(3) -- 設置排序順序,使用官方常用的3就行了。
  • 裝備/卸載物品

在裝備、卸載物品時,我們常常能看到人物的局部變化,這也是通過AnimState來完成的。
原理就是為Prefab的equipable組件設置裝備和卸載回調函數,在人物裝備、卸載物品時執行相應回調函數。
下面代碼片段中給出的就是回調函數,第一個參數inst是物品,第二個參數owner是人物。

其中核心語句是owner.AnimState:OverrideSymbol("swap_owner_symbol", "swap_build", "swap_symbol"),關于這一句代碼,請參照上文對OverrideSymbol方法的解釋。

對手持、身體和頭部裝備,有各自不同的固定代碼,分別列出如下

手持裝備

-- 裝備回調
local function onequip(inst, owner)
    owner.AnimState:OverrideSymbol("swap_object", "swap_build", "swap_symbol") --
    owner.AnimState:Show("ARM_carry")
    owner.AnimState:Hide("ARM_normal")
end

-- 卸載回調
local function onunequip(inst, owner)
    owner.AnimState:Hide("ARM_carry") -- 和上面的裝備回調類似,可以試試去掉的結果
    owner.AnimState:Show("ARM_normal")
end

身體裝備

-- 裝備回調
local function onequip(inst, owner) 
    owner.AnimState:OverrideSymbol("swap_body", "swap_build", "swap_symbol")
end

-- 卸載回調
local function onunequip(inst, owner) 
    owner.AnimState:ClearOverrideSymbol("swap_body")
end

頭部裝備

-- 裝備回調
local function onequip(inst, owner)
    owner.AnimState:OverrideSymbol("swap_hat", "swap_build", "swap_symbol")
    owner.AnimState:Show("HAT")
    owner.AnimState:Show("HAT_HAIR")
    owner.AnimState:Hide("HAIR_NOHAT")
    owner.AnimState:Hide("HAIR")
end

-- 卸載回調
local function onunequip(inst, owner)
    owner.AnimState:Hide("HAT")
    owner.AnimState:Hide("HAT_HAIR")
    owner.AnimState:Show("HAIR_NOHAT")
    owner.AnimState:Show("HAIR")
end
  • 按順序播放多個動畫

假如你有一組動畫需要連起來按順序播放,比如人物的一個完整的砍樹流程,有兩個動畫,一個是前搖chop_pre,一個是砍樹chop。這時候就需要考慮使用動畫隊列了。使用的方法很簡單,播放第一個動畫,然后把剩下的動畫推送到動畫隊列里。人物完整的砍樹流程動畫代碼如下:

    inst.AnimState:PlayAnimation("chop_pre") -- 播放前搖動畫
    inst.AnimState:PushAnimation("chop") -- 推送砍樹動畫
  • 播放完動畫就移除Prefab

在饑荒中,特效都是通過生成一個特效Prefab,播放特效動畫的方式來實現的。但很多時候我們只需要播放一次特效動畫,之后希望能移除這個Prefab。在動畫播放完成后,Prefab會自動推送一個animover事件。只要監聽該事件,在播放完后直接移除Prefab即可,具體代碼就一行:inst:ListenForEvent("animover", function() inst:Remove() end),通常直接寫在描述函數里,網絡代碼片段的下方,也就是只在主機執行這段代碼。

Physics-物理

Physics是為實體提供物理效果的組件,能夠使得實體擁有質量、半徑、速度和碰撞屬性,進而實現各種物理運動。

  • 設置物理類型

官方提供了標準化組件,可以通過一行代碼來設置各種不同類型的物理實體的碰撞屬性和質量、半徑等。
詳情可以參考游戲根目錄/data/scripts/standardcomponents.lua

這里講解實體物理類型的方法,這些方法不可在同一個Prefab中使用。使用方法是在描述函數中添加語句,需要寫在網絡代碼的上方。

物品欄物品(各種可以放進物品欄的小物品)
MakeInventoryPhysics(inst)
特點:可以通過inst.Physics:SetVel(x,y,z)來提供初速度,并且遵循重力、摩擦、碰撞等物理規律。

人物角色(人物,行走的生物)
MakeCharacterPhysics(inst, mass, rad)
其中,mass為質量,rad為碰撞半徑,下面類似參數名也有同樣含義。
特點:無視摩擦力,無法越過障礙物(小型:漿果叢,一般:池塘、圍墻)

飛行生物(蚊子,蜜蜂)
MakeFlyingCharacterPhysics(inst, mass, rad)
特點:類似人物角色,但可以越過像池塘、漿果叢這樣的障礙物。

極小飛行生物(蝴蝶)
MakeTinyFlyingCharacterPhysics(inst, mass, rad)
特點:類似飛行生物,但不會和飛行生物發生碰撞(很多蝴蝶可以在同一個位置重疊,而蜜蜂不行)

巨型生物(各大BOSS)
MakeGiantCharacterPhysics(inst, mass, rad)
特點:類似人物角色,但會越過漿果叢等小型障礙物。

飛行巨型生物(龍蠅,蜂后)
MakeFlyingGiantCharacterPhysics(inst, mass, rad)
特點:類似巨型生物,但可以越過池塘這樣的一般障礙物

幽靈(阿比蓋爾,蝙蝠,格羅姆,幽靈,玩家的靈魂)
MakeGhostPhysics(inst, mass, rad)
特點:類似人物角色,但無視障礙物。

障礙物(圍墻,各種建筑,豬王等等)
MakeObstaclePhysics(inst, rad, height)
特點:無

小型障礙物(漿果叢,尸骨)
MakeObstaclePhysics(inst, rad, height)
特點:無

重型障礙物(各種可以背的石塊)
MakeHeavyObstaclePhysics(inst, rad, height)
特點:類似障礙物,需要結合組件heavyobstaclephysics使用

小型重型障礙物(knighthead,bishophead,rooknose)
MakeSmallHeavyObstaclePhysics(inst, rad, height)
特點:類似小型障礙物,需要結合組件heavyobstaclephysics使用

  • 改變物理屬性

我們可以在游戲中動態地改變Prefab的物理屬性,以實現某種物理上的視覺效果,比如與豬王交易時,豬王拋出的物品就具有一個斜向上的初始速度,比直接在地上生成物品顯得更為自然。

無視碰撞
RemovePhysicsColliders(inst)
移除所有碰撞效果,自由穿梭

停止運動
inst.Physics:Stop()
會將實體的速度設為0

設置/獲取運動速度
inst.Physics:SetMotorVel(x,y,z)
為實體設置運動速度,驅使Prefab移動,x,y,z為各個軸向的速度。該方法只對人物、生物類型有效。
此處的坐標系為相對坐標系,x軸正向為Prefab當前面向方向,y軸向上,z軸方向由x,y用右手系法則確定。直觀地說,大多數時候我們只需要讓Prefab按照當前方向前進,那么只需要設置第一個參數的值為目標速度,y,z均取0。
inst.Physics:GetMotorVel()
獲取實體的當前速度

設置初速度
inst.Physics:SetVel(x,y,z)
為Prefab設置初始速度,驅使Prefab移動,x,y,z為各個軸向的速度。該方法只對物品欄物品類型有效。
SetMotorVel不同,此方法的坐標系與世界地圖的坐標系一致。
此速度只是初始速度,物品欄物品的運動會受到重力、摩擦力、彈力等影響。

Light-光

這個組件能允許實體提供照明功能,有各種參數來設定照明效果,學習這個組件主要是了解光源的各種參數。常見的提供光源的代表Prefab有火把、火坑、礦燈等等。

安裝組件inst.entity:AddLight()

啟用/禁用
inst.Light:Enable(bool)
bool為true,啟用組件,能發光。bool為false,禁用組件,不能發光。

判斷是否啟用了組件:inst.Light:IsEnabled()

Radius - 半徑
這一參數代表光照的范圍,半徑越大,光照的范圍越遠,注意這個半徑是三維的,也就意味著如果你搞個在半空中的光源的話,在地面上的光半徑是小于實際半徑的。

設置半徑:inst.Light:SetRadius(radius)
獲取半徑:inst.Light:GetRadius()
radius就是半徑,單位與游戲中的距離一致,取值為任意正數

以下為不同半徑的展示。
其它參數:intensity = 0.5,falloff = 0.5,RGB = (180 / 255, 195 / 255, 225 / 255)

radius = 1

radius = 10

Intensity - 光強
這里的光強并不是指光的強烈程度,而是指光線的集中程度。實際上,光強的提升是通過聚集更多的光線得到的,在其它參數相同的情況下,越高的光強,就意味著越集中的光線,過高的光強會使得光源附近的物體更黯淡。

可以類比生活中的聚光燈,越高的光強,就越集中在一點上。

設置光強:inst.Light:SetIntensity(percent)
獲取光強:inst.Light:GetIntensity()
percent為光強百分比,取值范圍為0到1之間。超出范圍的值,光強取0。
percent越接近1,光線越集中,中心位置越明亮,周圍也越暗;越接近0,光線越分散,中心位置越黯淡。當取為1時,會造成數據溢出,光線變成了一個四方形

以下為不同光強的展示
其它參數:radius = 1,falloff = 0.5,RGB = (180 / 255, 195 / 255, 225 / 255)

intensity = 0.1
intensity = 0.3
intensity = 0.5
intensity = 0.7
intensity = 0.9
intensity = 0.9999
intensity = 1

Falloff - 衰減
在生活中,對于一個單一發散的光源,比如蠟燭,距離越遠,光線就越暗。我們就用光線的衰減速度來描述光線隨著光源距離而變暗的動態變化。衰減速度越慢,就意味著能以更高的光強傳播到遠處。四周就顯得更明亮。

設置衰減:inst.Light:SetFalloff(percent)
獲取衰減:inst.Light:GetFalloff()
percent為衰減百分比,取值范圍為0到1之間。大于1則取1,小于0則取0。衰減越高,光線的傳播距離就越小。

在實際應用中,percent不可取值過小,因為光線的實際照明半徑有一定的計算公式,取過小的衰減會造成實際照明半徑溢出。通常來說,取值不可小于0.05

以下為不同衰減的展示
其它參數:radius = 1,intensity = 0.5,RGB = (180 / 255, 195 / 255, 225 / 255)

falloff = 0.1
falloff = 0.2
falloff = 0.3
falloff = 0.5
falloff = 0.7
falloff = 1

實際光照半徑

光照的實際范圍是由光照半徑,光強,衰減三個因素共同決定的。系統提供了一個獲取光照實際范圍的方法GetCalculatedRadius。

使用方法:inst.Light:GetCalculatedRadius()

Colour - 顏色
組件還允許設置光照的顏色,可以設置紅,綠,藍三種光,取值為0到1之間,但實質上,每個顏色的可能取值為256種,即 0/255 到 255/255。一般也按這個格式來寫具體參數。

設置顏色:inst.Light:SetColour(red,green,blue)
獲取顏色:inst.Light:GetColour()

雖然不同的顏色在游戲的視覺效果上有差異,但實質光照效果是不變的,這可以通過調用GetCalculatedRadius方法確認

Network-網絡

這一個組件本身有很多方法,但大多數是底層通信邏輯,與游戲功能沒有直接聯系,所以我們只需要簡單地添加一句inst.entity:AddNetwork(),為實體添加Network組件即可。
有無Network的區別在于這個Prefab是否會被其它玩家看到。比如著名的幾何種植Mod,種植時呈現出來的網格線也是一個Prefab,但它無法被其它玩家看到,也不需要和主機進行數據交換,就是因為沒有添加Network組件。
對于自定義的物品,我們當然是希望其它玩家也能看到的,更重要的是要和主機進行數據交換,所以是一定要添加Network組件。

Component

Component是一類用lua語言編寫的組件,可以在components文件夾下看到源碼各個Component的源碼。與Entity組件最大的區別是,Component不僅能調用方法,還能儲存數據,而無需依賴Prefab提供數據。這種設計能夠解耦功能與Prefab,使得同一個Component能夠被多個不同的Prefab使用,大大提高代碼的重用率。Component用到的數據由Component自行存取,邏輯上也更為清晰。比如,人物的血量就是一個Component,這個Component有兩個核心數據:當前血量和最高血量,相關的一系列方法都圍繞著這兩個核心數據進行操作。這個方法與人物本身是無關的,除了應用在人物上,同樣也可以應用在任何Prefab上。

Component的內容非常多,官方的Component已經達到了300多個,其中不少更是數百行代碼的巨大組件,完全足夠再開一章講解。此處只介紹如何添加和使用一個Component。后續講解Component時會有更詳細的解釋。

添加Component只需要一行代碼,inst:AddComponent("component名"),component名就是components文件夾下的各個component文件的名字。

所有的Component都由實體的components表統一管理,引用一個名為XX的Component的格式為inst.components.XX
調用XXComponent的YY方法:inst.components.XX:YY(參數列表)
調用XXComponent的yy屬性:inst.components.XX.yy

完善Prefab

在上面的內容中,主要是針對Prefab的組件功能進行了解釋,重點在于如何編寫描述函數內的代碼。
但還有一部分對Prefab的描述性內容,與Prefab的實際功能無關,不寫在描述函數里。比如,Prefab在游戲中顯示的名字,人物檢查Prefab時的描述,以及讓Prefab變成可以制作的東西等等。本節關注的就是這些內容。

描述

游戲中的許多描述性的內容,是通過全局變量STRINGS表來儲存的。游戲通過一系列的機制來獲取相應的字符串,所以只要按照特別的約定,修改STRINGS表的內容,就可以添加我們想要的描述了。

STRINGS.NAMES.LOTUS_UMBRELLA = “荷葉傘” -- 物體在游戲中顯示的名字
STRINGS.CHARACTERS.GENERIC.DESCRIBE.LOTUS_UMBRELLA = "這傘能擋雨嗎?" -- 物體的檢查描述
STRINGS.RECIPE_DESC.LOTUS_UMBRELLA = "荷葉做的雨傘" -- 物體的制作欄描述

名字

所有的Prefab在游戲中顯示的名字,都是由STRINGS.NAMES表儲存的。要添加我們自己的Prefab的描述性名字,只需要增加一個元素,key值取Prefab名的全大寫即可。比如荷葉傘的Prefab名為lotus_umbrella,則可以寫

STRINGS.NAMES.LOTUS_UMBRELLA = "荷葉傘"

人物描述

人物在檢查Prefab時,會說出一段描述性的文字。
游戲在取出描述文本時,首先檢查人物的特殊描述:STRINGS.CHARACTERS.人物Prefab名全大寫.DESCRIBE.物體Prefab名全大寫是否為nil,如果不為nil,就取這個文本。反之,則取通用描述:STRINGS.CHARACTERS.GENERIC.DESCRIBE.物體Prefab名。也就是說,你完全可以為一個Prefab設置多個人物描述。

同樣地,對于荷葉傘,可以添加通用描述:
STRINGS.CHARACTERS.GENERIC.DESCRIBE.LOTUS_UMBRELLA = "這傘能擋雨嗎?"
然后再對willow添加特殊描述
STRINGS.CHARACTERS.WILLOW.DESCRIBE.LOTUS_UMBRELLA = "荷葉傘?這不科學"

在制作人物MOD時,如果想為人物專屬物品添加人物專屬描述,可以把willow換成人物的Prefab名。

物品制作欄描述

和名字的設置方法是類似的,只是換了一張表。
代碼格式:STRINGS.RECIPE_DESC.物體Prefab名全大寫 = 描述字符串
仍拿荷葉傘舉例:STRINGS.RECIPE_DESC.LOTUS_UMBRELLA = "荷葉做的雨傘"

Recipe - 可制作

在游戲中,每一個可制造的物品都有一個Recipe。如果把游戲里的制作欄比作菜譜的話,那么一個Recipe就可以理解為菜單上的一道菜的描述說明。在制作欄上我們可以看到這個Prefab的名字,圖片和制作欄描述,以及制作需要的資源。還有另外一些隱藏的東西比如說所需的科技,一次制作能獲取幾個,誰能制作等等,這些都屬于Recipe的管轄范圍。

打開游戲根目錄/data/scripts/recipe.lua,可以看到Recipe的定義。構造函數的參數有name, ingredients, tab, level, placer, min_spacing, nounlock, numtogive, builder_tag, atlas, image, testfn。在定義Recipe時,我們并不需要填完所有的參數,只填我們需要的就可以了,不需要的參數,用nil來占位,最后幾個連續的nil則可以直接省略。

參數簡略含義如下

  • name:Prefab名
  • ingredients:成分表
  • tab:物品欄分類
  • level:科技等級
  • placer:建筑物放置物,也就是制造建筑時顯示的那個圖像(實質上也是個Prefab)
  • min_spacing:最小間隔
  • nounlock:是否可以離開制作臺制作--遠古物品只能在制作臺上制作。nil則可以離開制作臺
  • numtogive:制作數量,若填nil則為制作1個。
  • builder_tag:制作者需要擁有的Tag(標簽),填nil則所有人都可以做
  • atlas:制作欄圖片文檔路徑
  • image:制作欄圖片文件名,當名字與Prefab名相同時,可省略。
  • testfn:自定義檢測函數,需要滿足該函數才能制作物品,不常用。

在上面的參數中,name, ingredients, tab, level這些項是必填的,只要填了這四項就可以讓物品可制作了。其它參數都是選填的。

成分表ingredients較特殊,其中元素必須是Ingredient對象,也就是一個成分。構建一個成分十分簡單,只需要寫Ingredient(ingredienttype, amount, atlas, deconstruct)即可。其中ingredienttype是成分的prefab名或者特殊成分名(針對精神、血量等特殊成分),amount是所需數量,atlas是圖片文檔路徑。desconstruct很少使用,可以直接忽略。

對于MOD來說,官方提供了專門的MOD API,只需要使用這個MOD API來添加Recipe就行了,使用方法和Recipe的構造函數是一致的。

一般物品

要制作一般物品,只需要name, ingredients, tab, level,再添加atlas,用于顯示圖片即可。
在modmain里寫下相關代碼,使得荷葉傘可制作

modmain.lua

AddRecipe("lotus_umbrella", {Ingredient("cutgrass", 1), Ingredient("twigs", 1)}, RECIPETABS.SURVIVAL, TECH.NONE, nil, nil, nil, nil, nil,"images/inventoryimages/lotus_umbrella.xml") 

代碼中用到了RECIPETABSTECH這兩項常數,具體的值可以查閱游戲根目錄/scripts/constants.lua,如果要更換荷葉傘的制作歸類和科技等級,可以查閱這兩項常數的值。

建筑物

要制作建筑物,則比一般物品更復雜一些,需要先生成一個Prefab放置物(約定命名為Prefab名_placer,然后在添加Recipe時設置這個放置物Prefab。

下面通過講解制作一個荷花池(Prefab名為lotus_pond)來展示如何制作建筑物。

首先,需要在Prefab文件中,最后的return處添加語句,創建一個放置物:MakePlacer(name, bank, build, anim, onground, snap, metersnap, scale, fixedcameraoffset, facing, postinit_fn)

  • name:放置物的Prefab名,一般約定為原Prefab名_placer
  • bank:放置物的Bank
  • build:放置物的Build
  • anim:放置物用于播放的動畫,一般約定為idle
  • onground:取值為truefalse,是否設置為緊貼地面。請參考前面AnimState的內容
  • snap:取值為truefalse,這個參數目前無用,設置為nil即可
  • metersnap:取值為truefalse,與圍墻有關,一般建筑物用不上,設置為nil即可。
  • scale:縮放大小
  • fixedcameraoffset:固定偏移
  • facing:設置有幾個面,參考AnimState的內容
  • postinit_fn:特殊處理

雖然參數眾多,但對于一般的建筑物,只需要使用前5個參數:name, bank, build, anim, onground

對荷花池來說,需要添加的Placer語句為:MakePlacer("common/lotus_pond_placer", "lotus_pond", "lotus_pond", "idle", true)

lotus_pond_placer

然后按照一般的Prefab制作流程制作一個Prefab。因為荷花池需要緊貼地面,所以需要一些額外的AnimState設置,代碼如下:

    inst.AnimState:SetOrientation(ANIM_ORIENTATION.OnGround)
    inst.AnimState:SetLayer(LAYER_BACKGROUND)
    inst.AnimState:SetSortOrder(3)

然后,給荷花池添加一個Recipe,這個Recipe與一般物品不同,需要設置placer,添加語句:AddRecipe("lotus_pond", {Ingredient("shovel", 2), Ingredient("cutstone", 2),Ingredient("poop", 6)}, RECIPETABS.FARM, TECH.SCIENCE_TWO, "lotus_pond_placer", 5, nil, nil, nil,"images/map_icons/lotus_pond.xml")

作業

本章內容偏基礎知識,沒有太多可以實踐的東西,只需要做一個建筑 - 荷花池即可。要求能夠達到類似建造農場的效果:可以控制建造的位置,并且緊貼地面。

相關素材已經準備好了,直接編寫代碼即可。
動畫文件 - anim/lotus_pond.zip
圖片文檔 - images/map_icon/lotus_pond.xml

作業Mod在我的網盤下載,文件路徑為饑荒Mod指南/charpter_4_homework.zip,另外有答案文件,路徑為饑荒Mod指南/charpter_4_homework.zip

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,957評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,248評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,081評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,485評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,720評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,263評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,025評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,204評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,461評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,945評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,205評論 2 375

推薦閱讀更多精彩內容

  • 這個系列教程很長,涉及到很多編程、游戲方面的概念。與其讓你云里霧里地看著看著就放棄,不如先教你如何快速入門,通過模...
    LongFei_aot閱讀 12,878評論 9 74
  • 饑荒分為單機版和聯機版,一般來說,內容相同的MOD,聯機版相對單機版來說,總是要多一些對網絡數據的處理,總歸是要復...
    LongFei_aot閱讀 8,249評論 1 17
  • This article is a record of my journey to learn Game Deve...
    蔡子聰閱讀 3,835評論 0 9
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,156評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,684評論 25 708