Godot入門到棄坑:道具


3樓貓 發佈時間:2024-04-29 15:32:25 作者:cameLcAsE Language

輕鬆一下,做點簡單的東西。(我原本是這樣想的但是一看還是寫了這麼多字)
我在GDScript精要部分就用道具作為例子來講解了很多編程概念,現在我們來試著真正地實現一下道具。
“道具”在不同的語境中有不同的表現形式。一個是道具這一概念本身的各種屬性比如名字和描述,比如我們可能有一個道具欄,可以在其中看到這些信息。另一方面,道具可能會作為一個場景/節點出現在遊戲中,比如那些可以掉落可以被玩家拾取的道具,它們有一些視覺上的表現,但是它們總是會代表一個抽象上的道具。
一般來說在一個遊戲的某個具體的版本中,其中的道具是固定不變的。無論你的道具要出現在場景中還是道具欄中,無論它們要出現多少次,這些道具的概念上的、脫離具體表現形式的屬性應當是不變的,並且應當是各種場景實例所共享的。為了方便後續的描述,我姑且稱其為道具的“元數據”(metadata)。元數據就是描述數據的數據。
對於這種數據,我們可以用資源來表達。

資源

我們從啟動Godot的一瞬間我們就已經在和各種資源打交道。各種能通過磁盤存取的東西都可以是資源。也就是說,我們前面一直在使用的場景、腳本也是資源。各種Sprite節點的Texture屬性也是資源(注意它不能和圖片文件劃等號)。
正如文檔所說,資源是數據的容器。資源自身並沒有實際功能,但各種節點的功能需要通過資源來完成。
資源的一個特點是隻會加載一次。一旦一個資源加載到了內存中,後續對此資源的訪問都是指向同一個東西。

道具資源

作為遊戲設計師,我們會不斷設計出各種道具。這些道具有一些基本的屬性需要我們設計。比如一般來說它們可能有一個名字,有一個代表它的圖片,還有一段簡短的描述。
要新建一個(或者說”種“)資源,我們需要進行一些和新建場景不一樣的操作。前面提到資源都是可以存儲到磁盤中的,因此創建資源時我們需要在文件系統面板中進行操作,在文件系統面板中的某個位置單擊右鍵選擇新建資源:
新建資源和新建腳本一樣,需要為它選擇一個基類。我們在這裡可以看到各種各樣的內置資源類型,比如各種Texture、各種Shape,以及GDScript腳本。我們這裡要新建一種我們自己的資源,所以我們就選擇一切資源的基類Resource。然後給它取名,比如叫cherry。
現在你的文件系統中會出現一個新的tres文件——但是目前光是這個文件並沒有什麼實際用處。資源和節點一樣可以通過腳本來定義各種屬性和方法來實現它的功能。

添加腳本

要為資源添加腳本不像給節點添加腳本那樣在場景樹中直接點一下就好了。我們首先需要在文件系統新建一個腳本文件:
由於我們是在給資源創建腳本,所以繼承這一欄需要選擇Resource。我們這個腳本將會為各種道具所用,所以我們叫它item_metadata。
現在我們要把這個腳本附加到剛才新建的cherry資源上。我們點擊選中cherry資源,注意到右側的檢視面板:
在RefCounted一欄下可以看到Script屬性,在這裡我們可以直接把item_metadata腳本拖到這裡,也可以點擊箭頭加載腳本。這樣一來我們後續在腳本中編寫的代碼就會體現在cherry資源上了。
RefCounted是reference counted(引用計數)的意思。包括Resource在內的各種派生自RefCounted類都會記錄有多少對象引用了自己,當這個數字降到0時就意味著沒有人再使用它,它也就會被銷燬,佔用的內存也就會被釋放,而不需要手動地操作。

定義道具的數據模型

現在我們在腳本中定義描述一個道具的元數據所需要的屬性。現在我們能夠想到的是,道具有它的名字、圖片、描述、效果。我們定義如下屬性:
我們為每一個屬性都加上了export標記,這樣我們就可以在資源上編輯它們。現在選中cherry資源,檢視面板中應當出現了相應的條目。

直接從腳本創建資源

我們現在已經有了道具元數據的腳本,我們要創建新的道具資源,只需要新建資源然後把腳本像剛才那樣給它就行。不過這樣還是有點麻煩。
為了方便資源的創建以及在其它腳本中訪問資源,我們可以在剛才的item_metadata腳本中給它一個類名:
現在在新建資源時可以直接通過搜索找到這個類,創建一個自動關聯了此腳本的資源:

可拾取道具

新建一個場景,我們將會用它來表示玩家可以拾取的道具。
除了展示道具圖標的Sprite2D之外,這裡給它一個Area2D來檢測碰到了玩家。
請將Area2D的碰撞屬性配置為可以檢測到玩家的層。此外為了避免不必要的麻煩,也可以將Area2D的Monitorable取消勾選,這樣它就不會被其它Area檢測到。
這個Pickup將成為各種道具的容器,道具本身的元數據就來自我們自己創建的各種ItemMetadata資源。我們在Pickup的腳本中export一個類型為ItemMetadata的屬性,這樣就可以在檢視面板中填入某種具體的道具元數據,自然也可以通過代碼按需設置可拾取道具所包含的具體道具元數據。
有了道具的元數據之後,稍後我們就可以根據具體的道具元數據來顯示相關信息。這裡我們通過道具的元數據拿到道具對應的圖片,然後把它顯示到Pickup的Sprite2D中:
我們設想,當發現玩家進入拾取範圍時,我們就在UI上顯示一些和道具有關的信息,同時讓玩家知道有道具可以撿。
在玩家的腳本中,我們先增加一個變量用於表示身邊的可拾取道具。
在處理輸入的方法中,如果玩家按下了拾取的按鍵,且有道具可以撿,我們就撿起道具。現在我們暫時不管如何發揮道具的效果,只是簡單地讓它消失。這裡記得在項目設置中添加叫pick的Input Action並綁定到你喜歡的按鍵。
在Pickup的腳本中,連接Area2D的信號,如果發現玩家進入了範圍,那麼我們就設置玩家的item_to_pick屬性為這個道具。

“按F撿起”

有道具可撿時,我們顯示一個簡單的控件提示玩家可以撿起道具。我們新建一個UI控件場景,取名為PickupTip。然後添加節點如下:
前三個Label將分別用於展示道具的名稱、描述、效果,最後一個提示“按下F撿起”。你可以根據自己的喜好來調整控件的佈局。我就簡單讓VBoxContainer位於屏幕下半部分的某個地方。
給它添加一個腳本,添加對相關節點的引用備用:
隨後我們會根據具體的道具的元數據來設置這些Label顯示的內容。
我們還需要告知其它場景/節點什麼時候顯示這個UI。我們需要在Pickup中定義這樣一個信號,告知外界有玩家進入了拾取範圍:
這樣我們就可以在主場景中編寫相應代碼來顯示和隱藏拾起道具提示的UI。
我們之前把UI元素放在了單獨的一個CanvasLayer中,為了方便起見,在主場景腳本中添加一個對它的引用。拾取提示不是隨時都需要顯示,所以我們在需要時才實例化它。這裡又export一個變量來放拾取提示的場景。
現在的問題是,主場景如何連接上可拾取道具的信號呢?由於Godot的信號是各個對象的實例各自持有的,我們必須拿到一個具體的Pickup實例才能連接上它的信號。但是道具的生成可能會有多種方式,可能會預先放置在場景中,也可能在遊戲中由其它節點生成。所以這裡我們在主場景的腳本中連接上根節點的child_entered信號,它會在一個子節點進入場景樹時發出,我們可以檢查這個節點是不是Pickup,如果是我們就連接上它的player_entered信號:
具體的響應方法我們稍後補充。
在C#中,C#的事件可以是靜態的,也就是說可以不和任何實例關聯而僅和類關聯。在這種情況下我們可以把這個信號/事件定義為靜態的,這樣任意Pickup實例發出的信號都是同一個信號,玩家到底碰到哪個道具了由信號的參數體現。需要響應該信號的對象也不需要引用具體的Pickup實例,只需要連接這個靜態信號即可。但是目前GDScript還不支持靜態的信號,所以我能想到比較好的方法也只能這樣寫。
在PickupTip的腳本中定義一個item屬性用於存取其顯示的道具的元數據。在這裡我們單獨定義item屬性的setter(還記得這個東西嗎),每當外部為item賦值時,我們都更新相關Label中的內容:
GDScript的屬性有有一個比較好的特點是,可以不用單獨定義一個後備變量用到屬性的getter和setter中。直接在屬性的getter和setter中存取屬性自己不會發生無限遞歸,Godot會處理好這一點。
現在在主場景的腳本中,我們添加響應palyer_entered信號的方法:
如果需要顯示拾取提示時這個UI場景尚未實例化,我們就實例化並加入UI的CanvasLayer,否則我們就令其可見。稍後我們會通過設置visible屬性來控制拾取提示的顯示和隱藏。當然無論如何,我們都要設置拾取提示的item屬性為我們可以拾取的道具中所引用的道具元數據,以令其顯示對應道具的信息。

隱藏拾取提示

剛才的工作只做了一半。當玩家離開道具的拾取範圍時,我們要通過相關的信號來進行相關工作。玩家離開時,設置玩家可以拾取的道具為null,同時隱藏提示。代碼很簡單:
完全是同樣的代碼鏡像了一下。
至此,我們的道具拾取基本上就完成了:

讓道具發揮效用

按照設想,我們希望撿到櫻桃之後HP+1。
不過正如在之前的GDScript精要中提到的那樣,道具是一個很籠統的概念,它還可以細分為多種類別。比如貴重品(只能收集起來,可能不能交易和使用)、消耗品(可以使用,有一定的效果),裝備也可以算作道具的一個類別。
這裡為了簡單起見,我們單獨從ItemMetadata派生出一種HpItem,它包含使用後hp的變化量:
新建腳本時,繼承的類選擇之前的ItemMetadata。
由於這個新的資源類型是派生出來的,所以沒太多需要增加的。我們只是增加一個hp量的屬性。int類型可正可負,沒毛病,有可能我們會需要做一些會減少hp的道具。
最後修改現有的cherry資源,這裡直接在其檢視面板中修改它關聯的腳本為新的HpItem(選擇Quick Load或者Load加載現有的腳本):
由於存在繼承關係,之前的一些屬性都還在,如果圖片沒了就重新給它加上去,除此之外只需要修改Hp Amount為1即可。
之前玩家撿起道具後除了準備銷燬撿起的道具節點之外什麼都沒做,現在我們來把這個櫻桃吃掉。當我們發現可以撿起的道具是HpItem時,我們就修改玩家的hp。這一次我們遵循前面PikcupTip的item屬性的套路,把玩家的hp也改成能夠執行額外工作的屬性——讓hp發生變化時即發出hp_changed信號:
由於之前的HP指示器UI本身就是連接到了hp_changed信號,所以不需要額外的修改。
現在再修改撿起道具的代碼:
只要我們發現可以撿的道具代表的是HpItem,我們就可以修改玩家的hp。同時我們把新的hp的值限定到了0和HP最大值之間。當然這裡具體怎樣處理取決於你的設計。
完成!


© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com