lua游戏开发实践指南光盘_Godot游戏开发实践之三:容易被忽视的Resource
一、前言
首先,特大喜訊,奔走相告, Godot 愛好者們又有新的窩了——我們國人自建的 Godot 論壇:Godot中文社區(qū)已經(jīng)正式開放,這里有一手的開發(fā)資源,最新的科技動向,開發(fā)上有啥問題可以隨時發(fā)帖,歡迎大家隨時到論壇來討論、交流和學(xué)習(xí)游戲開發(fā)的最新技術(shù)。
那么,回過頭來,今天要探討的話題是 Godot 中極容易被新手忽視的 Resource 資源類。開發(fā)過 Unity 游戲的同學(xué)們知道一個叫 ScriptableObject 的很有用的類,它可以用于數(shù)據(jù)的包裝,在不少場合中應(yīng)該是非常有用的,那么在 Godot 中有沒有這個類似的特性呢?嗯,也有,這就是我們今天要談到的 Resource 資源類型。
官網(wǎng)也有對 Resources 的相關(guān)介紹,我們知道場景是不能拖拽的,也是固定不變的,如果要用場景來保存一些普通數(shù)據(jù),肯定不太合理,這時候我們可以使用 Resource 資源類。相比 Node 其優(yōu)點也很明顯,使用非常靈活,同樣可以編寫腳本,可以定義屬性和方法,創(chuàng)建資源文件方便,直接拖拽應(yīng)用即可。"OK, FINE!" 這些我都會談到,更重要的是,我今天會利用 Resource 提出一個全新的、靈活的、“強力”解耦的 EventBus 全局事件模式。感興趣嗎?那我們繼續(xù)。
主要內(nèi)容:Resource 的相關(guān)用法簡介閱讀時間:8 分鐘永久鏈接:http://liuqingwen.me/2020/08/17/godot-game-devLog-3-talk-about-resource/系列主頁:http://liuqingwen.me/introduction-of-godot-series/
二、正文
Resource 并不神秘,但是很容易被忽視。其實我們平時創(chuàng)建的場景、節(jié)點中就包含了各種不同類型的資源文件,官網(wǎng)中的一張圖展示了某些節(jié)點 Node 和資源 Resource 的關(guān)系:
相信上圖中的名稱都不陌生,游戲場景開發(fā)過程中可能會使用上多種資源類型,常見的就有:圖片資源、碰撞圖形、各種材質(zhì)、 UI 主題、音頻流、漸變、曲線等等,甚至我們常用的 AnimationPlayer 節(jié)點中創(chuàng)建的動畫,以及 GDScript 腳本、著色器代碼也都是資源。
資源的創(chuàng)建和使用也非常簡單,不過,目前在 Godot 3 版本中也存在一些局限性,接下來我們詳細聊聊。
Resource 的創(chuàng)建與使用
創(chuàng)建 Resource 資源的方式就有多種,平常都是在 Node 節(jié)點的屬性面板中直接創(chuàng)建,比如 New 一個玩家的碰撞體圖形的形狀,或是動畫播放器中的各種動畫,粒子系統(tǒng)新建的材質(zhì)等等,這些資源有一個特點:我們開箱即用,很少保存。
資源文件也可以單獨創(chuàng)建,假設(shè)我們需要創(chuàng)建一個需要在很多地方使用的資源,比如通用的主題資源、字體資源、瓦片地圖 TileSet 資源等等,那么我們可以單獨創(chuàng)建相應(yīng)類型的資源文件,保存起來,在不同場景中輕松實現(xiàn)重復(fù)利用。在屬性面板或者節(jié)點屬性中都可以新建資源文件:
新建資源文件后記得保存,保存的文件后綴名一般是?.tres?也有?.res?文件類型的,區(qū)別在于以文本格式保存還是二進制文件格式保存:
保存好的資源文件我們可以隨時修改其相關(guān)屬性值,雙擊資源文件即可,另外,也可以創(chuàng)建多個副本,比如字體資源復(fù)制( duplicate )一份,然后修改字體大小屬性,使用在不同的地方。
資源的使用方式就簡單了,可以直接拖拽到對應(yīng)屬性中,也可以在屬性下拉列表中點擊 Load 加載。系統(tǒng)自帶的資源比較齊全,當然我們也可以自定義資源類型。資源從本質(zhì)上來說仍然是一種腳本文件,創(chuàng)建自定義資源首先需要創(chuàng)建一個繼承自?Resource?類的腳本:
# 繼承自 Resource 說明這是一個資源腳本extends Resourceclass_name CustomResource, 'res://CustomResource/custom_icon.svg'# 資源也可以定義普通的屬性export var variable1 := ''export var variable2 := 0# ...# 資源也可以定義一些方法func printInfo() -> void:# ...在上面新建的代碼中我們聲明了資源的類名(?CustomResource?)以及資源的圖標(res://CustomResource/custom_icon.svg?)。創(chuàng)建好之后,可以在新建資源列表中發(fā)現(xiàn)相對應(yīng)的自定義資源類型,這一系列過程可以參考下圖:
是不是非常簡單?趕緊動手創(chuàng)建一個壓壓驚。
Resource 相關(guān)問題與局限
資源的創(chuàng)建和使用確實簡單,不過 Godot 3 中對于自定義資源還是有點小坑,這里提出來,希望對新手朋友們有用。
1. 不能使用自定義 Resource 為變量類型
我們創(chuàng)建自定義資源時,可以給資源定義個類名?class_name CustomResource?,但是在代碼中確不能定義該類型的資源變量:
var resource1 : Resource # 沒問題var resource2 : CustomResource # 不支持!上面的代碼運行會報錯:
built-in:4 - Parse Error: Invalid export type. Only built-in and native resource types can be exported.
避免這個問題的方法就是使用父類型?Resource?作為變量的類型,不過這樣會導(dǎo)致在export?屬性中可以賦予任意類型的資源文件,非常不方便、不人道。當然你可以在代碼中進行判斷:
if resource && resource is CustomResource:# 代碼...不過,好消息是這個問題會在 Godot 4.0 中得到解決。
2. 使用 Resouce 要注意資源是引用類型
如果一個資源文件被多個節(jié)點使用,這個時候你只要改變了某個節(jié)點下該資源的任意一個屬性,結(jié)果都會導(dǎo)致其他節(jié)點下該資源跟隨發(fā)生變化!
舉個例子,游戲資源中有一個?font_resource.res?字體資源文件,當你改變了資源屬性中字體的大小后,其他所有使用了該資源的 UI 界面字體都會發(fā)生改變。這也是為什么新手們經(jīng)常會遇到這種情況:*創(chuàng)建一個節(jié)點,添加碰撞體,新建一個碰撞體圖形,設(shè)置好之后復(fù)制該節(jié)點并重命名,修改新碰撞節(jié)點的圖片和碰撞體圖形,莫名發(fā)現(xiàn)之前節(jié)點的碰撞體圖形也發(fā)生了改變*,其實就是這個原因。
所以,在 Godot 中一個小小的變量值改變都需要重新創(chuàng)建一個資源,這也不算什么大問題,我們可以右鍵資源文件?Duplicate?復(fù)制一個,或者使用?Make Unique?方式使指定資源唯一化。
3. 使用 Resouce 要注意避免循環(huán)引用
如果你的項目中創(chuàng)建了不少自定義資源文件,自定義資源代碼中又引用了其他類型的資源,那么有可能會出現(xiàn)這種錯誤;
"scene/resources/resource_format_text.cpp:1387 - Circular reference to resource being saved found: 'res://src/.../???.tres' will be null next time it's loaded."
其實循環(huán)引用問題(?Circular reference?)在普通 GD 代碼中也會出現(xiàn),而出現(xiàn)在自定義資源中則會變得難以發(fā)覺。解決這個問題的方法就是不要在編輯器中直接給資源賦值,轉(zhuǎn)而在運行時判斷然后動態(tài)加載 Resource ,示例如下:
export var resource : Resource # 自定義資源export var resourceFilePath : String # 資源路徑func method() -> void:if resource == null:# 運行時加載資源文件resource = load(resourceFilePath)# 代碼...這種情況應(yīng)該比較少見,暫時不做深入討論,后面的文章遇到了再詳述,當然,我們翹首以待的 4.0 版本會解決這個問題。
4. 其他的小問題
如果修改資源腳本中的圖標或者類名后,其他引用了這個 Resource 的代碼就會報錯,類似?Resource 類已經(jīng)損壞,加載不完整之類。重新啟動項目就可以了。
有時候還會遇到這種小 BUG :
core/script_language.cpp:244 - Condition "!global_classes.has(p_class)" is true. Returned: String()
有點莫名,也不容易重現(xiàn),我估計是修改了 Resource 腳本類名引起的,反正重啟項目就沒事了。
這些小問題說明目前 Godot 的資源類型還不夠完善, Waiting for Godot 4.0 藥到病除,哈哈!
創(chuàng)建 Resource 相當于 DataContainer
創(chuàng)建自定義 Resource 的一個經(jīng)典用途就是當做數(shù)據(jù)容器。創(chuàng)建一個個資源文件就相當于創(chuàng)建了一個個數(shù)據(jù)容器,這些數(shù)據(jù)容器一般沒有其他功能,只是獨立保存一些應(yīng)用數(shù)據(jù),不論是修改還是使用都非常方便且靈活。
舉個具有實際應(yīng)用場景的例子,在一個 Player 或者 AI 腳本中,如果存在著大量數(shù)據(jù)屬性,而這些數(shù)據(jù)屬性一般不會發(fā)生改變,或者只是一些配置參數(shù),那么我們完全可以將其抽離出來作為一個單獨的數(shù)據(jù)類——這也是《重構(gòu)-改善既有代碼的設(shè)計》一書中提倡的重構(gòu)方式之一。
# 玩家類export var name := 'player'export var moveSpeed := 200export var rotateSpeed := 5# 其他一些屬性...在 Godot 中這個所謂的單獨數(shù)據(jù)類可以使用內(nèi)部類進行包裝:
# 玩家類# 內(nèi)部類class Data:var name := 'player'var moveSpeed := 200var rotateSpeed := 5func _init():pass內(nèi)部類雖然可以封裝數(shù)據(jù),但是在腳本范圍之外使用則非常蹩腳,也不方便在編輯器中進行編輯,這時候我們可以使用自定義資源類解決這個痛點:
extends Resourceexport var name := 'player'export var moveSpeed := 200export var rotateSpeed := 5然后創(chuàng)建單個或者多個資源文件,在編輯器的屬性面板中修改對應(yīng)的屬性值,在其他代碼中使用起來非常方便:
export var dataResource : Resource = nullfun _ready() -> void:if dataResource != null:print(dataResource.name, dataResource.moveSpeed, dataResource.rotateSpeed)作為數(shù)據(jù)容器和 ScriptableObject 有點類似,接下來我們看 Resource 的另一個非常有用的場景。
用 Resource 創(chuàng)建全局事件的 EventBus
可以說這是本文的重點,目前我還沒有看到有任何人在項目中使用過這種方式,且聽我慢慢道來~~~
首先,關(guān)于 Godot 中的?signal?信號以及觀察者模式相信大家都已經(jīng)駕輕就熟了,一般在游戲開發(fā)中我們都會準守?signal up, call down?的準則,即往上層發(fā)送信號,往下層直接調(diào)用。當游戲變得越來越復(fù)雜的時候,信號可能已經(jīng)充滿了整個項目,比如某個多人游戲中信息面板需要接收并顯示多種不同類型的信號:玩家按下回車鍵發(fā)送的文字信息、玩家某個戰(zhàn)場獲得勝利發(fā)出的信號、某個玩家退出游戲發(fā)出的信號、官方服務(wù)器推送的信息等等,因為這些信息發(fā)生在不同的場景,處理起來并不簡單,我能想到的解決方式有這么幾種:
使用?get_node('../root/node_path')?方式,不推薦并表示強烈譴責,這會造成強耦合,擴展、維護和重構(gòu)極其困難
使用?Global AutoLoad?,也就是 Singleton 單例模式,有效解決耦合,但是維護相當困難,牽一發(fā)而動全身,調(diào)試困難
使用 Resource 創(chuàng)建相應(yīng)的事件資源,強力解耦,使用起來非常方便,調(diào)試也非常簡單,易擴展和維護
關(guān)于第二種方式是大家推薦的模式,我在之前的示例中就使用過:(Godot游戲開發(fā)實踐之一:使用High Level Multiplayer API制作多人游戲(上)), GDQuest 的文檔中也介紹了這種模式:https://www.gdquest.com/docs/guidelines/best-practices/godot-gdscript/event-bus/?,示例代碼如下:
# 這是一個 AutoLoad 單例extends Node# 可以定義多個通用信號signal new_message(content)# 其他代碼...其他場景中使用也非常簡單:
# 場景 1 中發(fā)送信號:GameConfig.emit_signal('new_message', '......')# 場景 2 中接收處理信號:GameConfig.connect('new_message', self, '_on_NewMessage_arrive')但是這種方式有一個很大的缺陷:全局引用導(dǎo)致重構(gòu)困難。因為單例相當于全局模式,任何地方都可以引用,重構(gòu)時一旦改動單例中某個方法或者屬性都有可能引起其他地方因為引用失效而導(dǎo)致運行奔潰,尋找這些引用并不容易,這也為什么 GDQuest 推薦的 EventBus 模式是單獨創(chuàng)建的只有信號沒有其他代碼的腳本文件。
廢話一堆,一起來看看利用 Resource 創(chuàng)建的事件模式吧!首先創(chuàng)建一個事件資源:
# 自定義資源extends Resourceclass_name EventResource, 'res://EventResource/event_icon.svg'# 自定義信號signal custom_event(type, message)# 可以定義一些屬性export var type := 'defaultEvent'# 自定義方法用于發(fā)送信號的包裝,也可以直接發(fā)送信號func emitSignal(object) -> void:self.emit_signal('custom_event', type, object)接下來,我們可以創(chuàng)建一些事件資源文件,比如?message_event.trestrigger_event.tres?,不同的文件可以更改、配置不同的參數(shù),然后在其他腳本中使用:
export var messageEvent : Resource = nullexport var triggerEvent : Resource = null# 可以使用事件資源偵聽事件func someMethod1() -> void:if triggerEvent && triggerEvent is EventResource:triggerEvent.connect('custom_event', self, '_onTriggerEventHandler')# 也可以使用事件資源發(fā)送事件func someMethod2() -> void:if messageEvent && messageEvent is EventResource:messageEvent.emitSignal(info)因為這些事件都是資源類型,在節(jié)點屬性中可以直接拖拽使用,而且可有可無,均不影響整個項目的運行,在本示例中玩家的屬性配置如下圖:
可以看到?Player1?只接收?message_event?事件,?Player3?只派發(fā)trigger_event?事件,而?Player2?則無任何配置,可謂一目了然。
總結(jié)一下使用 Resource 創(chuàng)建事件的一些優(yōu)點:
強力解耦!不依賴其他文件或者腳本、節(jié)點,很容易進行重構(gòu)
便于調(diào)試,代碼中只要注意?null?引用即可,刪除或者添加相關(guān)事件都非常友好
便于測試,修改事件相關(guān)屬性值非常方便,一改全改
可以考慮在大型項目中應(yīng)用
并沒有十全十美的萬能解決方案,當然也是有缺點的,比如一堆的只是改變了某一個變量值的?.res?文件等。重要的是,目前還沒有實際項目支持這個事件模式,有待大家的開發(fā)和探索啊。
三、總結(jié)
好了,這篇就聊了一個簡單的 Resource 話題,希望能給新手朋友們帶來一點點幫助,給高手朋友們開拓一點點亮光,那這篇文章也就值了。
記住我們 Godot 愛好者的新家:Godot中文社區(qū)( http://godoter.top/ )?,歡迎常回家看看!
本篇的 Demo 以及相關(guān)代碼已經(jīng)上傳到 Github ,地址:https://github.com/spkingr/Godot-Demos , 后續(xù)繼續(xù)更新,原創(chuàng)不易,希望大家喜歡!
我的博客地址:http://liuqingwen.me ,我的博客即將同步至騰訊云+社區(qū),邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=3sg12o13bvwgc ,歡迎關(guān)注我的微信公眾號(第一時間更新+游戲開發(fā)資源+相關(guān)資訊):
新人創(chuàng)作打卡挑戰(zhàn)賽發(fā)博客就能抽獎!定制產(chǎn)品紅包拿不停!總結(jié)
以上是生活随笔為你收集整理的lua游戏开发实践指南光盘_Godot游戏开发实践之三:容易被忽视的Resource的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 半年辞退30多个程序员,大厂“开猿节流”
- 下一篇: 2021年度最佳开源软件榜单出炉!