自從9月12日Unity公布了新的收費(fèi)模式后窘游,一部分開發(fā)者開始轉(zhuǎn)向免費(fèi)開源的Godot引擎针史。為了讓開發(fā)者少走一些彎路斩披,Godot創(chuàng)始人Juan Linietsky這幾天發(fā)布了一系列推文秋泳,針對引擎各方面做了一些答疑,很多東西是官方文檔沒有提到的乡革,更偏向于“在引擎作者眼中這個引擎應(yīng)該怎么用”,值得翻譯記錄一下摊腋。
包含以下內(nèi)容:
- 概念上的對應(yīng)關(guān)系
- 場景樹理念與Unity的區(qū)別
- GDScript還是C#
- 如何應(yīng)對高性能需求
- Servers API的使用(繞過場景系統(tǒng)直接使用底層API以達(dá)到極致性能)
概念上的對應(yīng)關(guān)系
實(shí)體:節(jié)點(diǎn)
組件:節(jié)點(diǎn)
場景設(shè)置:節(jié)點(diǎn)
導(dǎo)航:節(jié)點(diǎn)
光照貼圖:節(jié)點(diǎn)
視口:節(jié)點(diǎn)
行為:節(jié)點(diǎn)+腳本
預(yù)制體:場景
場景組合:場景
ScriptableObject:資源
幾乎所有內(nèi)容都是節(jié)點(diǎn)沸版、場景或資源...Unity中很多非常復(fù)雜的子系統(tǒng),在Godot中它們表達(dá)得更加自然和直觀兴蒸。
因此视粮,我建議新用戶首先熟悉 Godot 的工作原理及其背后的價(jià)值是什么。我理解(想盡快遷移到Godot的)這種沖動橙凳,但僅僅是把Godot當(dāng)成Unity一樣來轉(zhuǎn)換游戲工程蕾殴,很可能會導(dǎo)致很多痛點(diǎn)笑撞。
對于場景樹的建議
我能給那些從 Unity 轉(zhuǎn)向 Godot 的人的最好建議是:
你必須將Godot的“場景樹”想象為“一棵不包含任何實(shí)體的組件樹”。這有兩個特殊點(diǎn):
- 場景一目了然钓觉,更加清晰
- 組合更加靈活
你不需要在每個節(jié)點(diǎn)上掛多個腳本來實(shí)現(xiàn)某種行為茴肥,只需要添加更多子節(jié)點(diǎn),子節(jié)點(diǎn)上再掛腳本即可荡灾。
此外瓤狐,預(yù)制體或場景組合,在概念上是并不存在的批幌。你可以簡單地在其他地方實(shí)例化或繼承任何場景础锐。你還可以讓它們的實(shí)例變得可編輯并做一些本地更改。
譯注:Godot中荧缘,一個節(jié)點(diǎn)上只能掛一個腳本郁稍,相比Unity中腳本是作為組件掛載到物體上,Godot中的腳本更像是節(jié)點(diǎn)功能的擴(kuò)展
最后胜宇,要理解每個場景沒有“全局設(shè)置”的概念耀怜,場景只是節(jié)點(diǎn)。 Unity 中屬于場景的事物桐愉,例如光照貼圖财破、導(dǎo)航、環(huán)境等从诲,在Godot 中仍然只是節(jié)點(diǎn)左痢,允許根據(jù)需要混合和匹配任何內(nèi)容。
即使你在 Godot 中加載場景系洛,引擎也會將其放置在一個“根”節(jié)點(diǎn)下俊性。什么是根節(jié)點(diǎn)?一個窗口節(jié)點(diǎn)描扯!沒錯定页,如果你想在游戲中使用多個窗口,請實(shí)例化更多窗口并將子節(jié)點(diǎn)放入其中绽诚。
個人認(rèn)為這個是Godot非常有特色的地方典徊,甚至有人做出了可以在多個窗口之間跳躍的2D平臺類游戲
這些事情需要一些時(shí)間才能理解,但最終發(fā)生的事情是 Godot 讓你徹底顛覆游戲開發(fā)的過程:
- 在 Unity 中恩够,你用代碼設(shè)計(jì)游戲卒落,并使用編輯器作為一個工具
- 在 Godot 中,你在編輯器中設(shè)計(jì)游戲蜂桶,然后添加代碼..
由于 GDScript 實(shí)際上是編輯器緊密集成的一部分(并且該語言是深度集成的)儡毕,因此使用它進(jìn)行開發(fā)的體驗(yàn)甚至比使用在單獨(dú)的 IDE 中編輯代碼的引擎更加流暢。這就是大多數(shù) Godot 用戶更喜歡它的原因扑媚。
這些概念上的差異有助于大多數(shù) Godot 用戶提高工作效率腰湾。你很可能注意到一些 Godot 項(xiàng)目贾费,甚至是那些沒有太多使用該引擎經(jīng)驗(yàn)的用戶制作的項(xiàng)目,在今年過去的大型游戲大賽(GMTK檐盟、Ludum Dare 等)中取得了不錯的成績褂萧。
所以再次強(qiáng)調(diào),在嘗試從 Unity 1:1 轉(zhuǎn)換你的游戲之前葵萎,在移植項(xiàng)目之前导犹,請花點(diǎn)時(shí)間采取行動并充分理解 Godot 的設(shè)計(jì)哲學(xué)。我最不希望的是 Unity 用戶在嘗試過程中受到傷害并獲得糟糕的體驗(yàn)羡忘。
關(guān)于腳本語言GDScript
為什么 GDScript 存在谎痢?在 Godot 的背景下,有兩個主要原因是用戶的首選:
- 快速迭代
- 深度融合
快速迭代: 當(dāng)你忙于開發(fā)游戲時(shí)卷雕,代碼部分不會妨礙你:
- 無構(gòu)建時(shí)間节猿,即時(shí)運(yùn)行
- 集成編輯器,快速打開附加到節(jié)點(diǎn)的腳本
- 立即重新加載正在運(yùn)行的游戲中的更改(熱重載)
- 同時(shí)調(diào)試游戲數(shù)據(jù)和代碼..
- 沒有GC..
GDScript使用引用計(jì)數(shù)漫雕,而不是垃圾回收器:內(nèi)存管理 滨嘱。根據(jù)官方文檔介紹,是為了避免GC工作時(shí)引起的卡頓和不必要的大量內(nèi)存占用浸间。
深度集成:
- 大量與引擎緊密相關(guān)的特性:節(jié)點(diǎn)路徑語法太雨、onready、IDE中的可視化連接魁蒜、預(yù)加載關(guān)鍵字等等
- 與引擎共享數(shù)據(jù)模型囊扳,允許零消耗的查看變量、序列化兜看、網(wǎng)絡(luò)化等
- 將變量暴露給編輯器無需膠水層
節(jié)點(diǎn)路徑語法:
例如有一個這樣的場景:
使用$
加上路徑即可獲取子節(jié)點(diǎn)锥咸,也可以將節(jié)點(diǎn)拖入代碼編輯器中自動生成代碼:
var col = $CharacterBody2D/CollisionShape2D
集成甚至更加深入:
- 代碼自動完成可以自動提示游戲數(shù)據(jù)(節(jié)點(diǎn)路徑,文件路徑细移,動畫名稱搏予,對象中的實(shí)時(shí)數(shù)據(jù)等)
- 可以從編輯器拖入各種東西到代碼中,自動生成相關(guān)代碼(節(jié)點(diǎn)路徑葫哗、文件路徑缔刹、屬性等)
- 內(nèi)置的代碼編輯器和檢查器
總而言之,Godot 用戶更喜歡使用 GDScript劣针,因?yàn)檫@種針對引擎量身定制的深層次集成。就像虛幻中的藍(lán)圖亿扁。 所以捺典,Godot 并不是打算讓 C# 成為二等公民,C# 已經(jīng)盡可能地集成了(但沒辦法達(dá)到GDScript的高集成度)从祝。
個人看法和建議:
雖然個人更熟悉C#襟己,但在上手Godot的過程中依然是先使用了GDScript引谜,包括參加Game Jam、制作一些個人項(xiàng)目擎浴,都是使用GDScript员咽。如果你之前使用過Python或Lua,那么GDScript是非常容易上手的贮预,給它一些嘗試贝室,說不定它是你的菜。
正如上面所說仿吞,GDScript與引擎的集成度是最高的滑频,對開發(fā)效率有不錯的提升,相比C#在開發(fā)中沒法享受到高集成度帶來的便利(例如拖拽生成代碼唤冈、可視化信號連接等等)峡迷。
性能上根據(jù)社區(qū)做的測評,C#是比GDScript快的你虹,如果非常注重性能绘搞,C#是更好的選擇;如果想在GDScript中提升性能傅物,那么使用靜態(tài)類型看杭,避免使用動態(tài)類型。
如果對強(qiáng)類型有要求挟伙,或者希望代碼盡可能與引擎解耦楼雹,這種情況下GDScript可能不是好的選擇,雖然支持靜態(tài)類型尖阔,但它本質(zhì)上還是動態(tài)類型語言贮缅,不支持接口(只有鴨子類型),沒有很強(qiáng)的類型檢查介却;與引擎集成度高也導(dǎo)致它更難與引擎解耦谴供。
Godot 3.x LTS 版本下,C#支持全平臺打包齿坷,在目前最新版Godot 4.1中桂肌,C#不支持移動端和WebGL打包。這是由于3.x 使用的是Mono永淌,而現(xiàn)在Mono已廢棄崎场,4.x版本改用.Net,沒有IL2CPP那樣的魔法加持遂蛀,需要等待微軟官方做移動端和WebGL的支持谭跨,預(yù)計(jì)時(shí)間是今年底。
多種開發(fā)語言問題
我從 Unity 開發(fā)人員那里看到的一個持續(xù)的擔(dān)憂是,由于 Godot 設(shè)計(jì)為支持多種語言(不僅僅是 C#)螃宙,這將導(dǎo)致插件碎片化蛮瞄。 Godot 使用通用語言適配器 API,因此目標(biāo)是你可以使用任何語言的任何插件谆扎。
雖然這仍然不是 100% 完善(現(xiàn)在 GDScript 可以使用 C# 和 C++ 插件挂捅,C# 只能使用 C++ 插件,但兩者都不能使用 GDScript)堂湖,在架構(gòu)上已經(jīng)可以實(shí)現(xiàn)這一點(diǎn)闲先, Godot 中的所有語言都使用相同的引擎 API。
老實(shí)說苗缩,我希望大多數(shù)復(fù)雜的附加組件都用 GDExtension (C++/Rust) 或 C# 編寫饵蒂。 借助他們都已經(jīng)使用的通用 Godot 語言 API 適配器,應(yīng)該很容易實(shí)現(xiàn)互通酱讶。
好奇這是如何運(yùn)作的退盯?基本上,Godot 4 使用 C 中公開的 API 適配器:
https://github.com/godotengine/godot/blob/master/core/extension/gdextension_interface.h
C 非常高效泻肯,這個星球上的每種語言編程都可以與它交互渊迁。此外,C 是 ABI 穩(wěn)定的灶挟,確保當(dāng)前和未來的互操作琉朽。
順便說一下,在編寫或使用插件時(shí)稚铣,這對你來說是完全透明的箱叁,你將在文檔中看到 API,并且只需從你喜歡的語言中使用它即可惕医。
性能問題
我從遷移到Godot的Unity用戶那里看到的另一個常見主題是耕漱,Godot是如何處理類似用Burst/ECS編寫的代碼的? 這是以不同的哲學(xué)方式處理的抬伺,節(jié)點(diǎn)沒問題螟够,但是如果處理數(shù)萬個節(jié)點(diǎn),性能就會受到影響峡钓,那么該怎么辦妓笙?
在 Godot 中,有兩種方法可以獲得“更快”的性能能岩,特別是對于大量實(shí)體:
- 用底層語言重寫腳本(C++/Rust/等)
- 使用Servers API
正如我之前提到的寞宫,Godot 使用通用語言適配器 API。這意味著捧灰,如果你有一個帶有腳本的節(jié)點(diǎn)并且需要對其進(jìn)行優(yōu)化淆九,你可以簡單地用更快的語言(C++统锤、Rust毛俏,甚至 C#)重寫它炭庙,并且對于其余代碼來說它將是透明的。
一般來說煌寇,需要優(yōu)化的部分很谢捞恪(比如永遠(yuǎn)不會超過整個游戲的 5%),其余部分 GDScript 可以很好地處理阀溶。 但如前所述腻脏,如果使用節(jié)點(diǎn),在處理數(shù)以萬計(jì)的實(shí)體時(shí)可能會遇到問題银锻,這種情況下應(yīng)該使用Servers API永品。
Godot 提供了兩個抽象層:場景層(Scene)和服務(wù)層(Servers)。場景及其節(jié)點(diǎn)是一個高級的击纬、非常靈活的抽象鼎姐。但Godot中所有的底層操作都是在服務(wù)層完成的。在Godot中更振,你可以輕松繞過場景層炕桨,直接使用服務(wù)層。
使用底層語言和Servers API肯腕,你可以獲得最大性能献宫,并且仍然保留使用 Godot 的所有可移植性和易用性優(yōu)勢,同時(shí)該代碼可以與游戲的所有高級代碼順利交互实撒。
Godot 4.x支持Compute Shader姊途,但這里沒有提到太多
下面是對Servers API文檔的部分翻譯
使用Servers優(yōu)化性能
就像大多數(shù)引擎那樣,Godot的場景系統(tǒng)使用節(jié)點(diǎn)與資源來簡化項(xiàng)目內(nèi)容的組織和資產(chǎn)的管理知态,以此制作復(fù)雜的游戲捷兰。但是顯然,這樣做有以下缺點(diǎn):
- 這又會導(dǎo)致一層額外的復(fù)雜性
- 性能比直接使用簡單 API 時(shí)要低
- 不可能使用多個線程來控制它們
- 需要更多的內(nèi)存
大部分情況下肴甸,這并不是問題(Godot 進(jìn)行了非常多的優(yōu)化哆姻,大多數(shù)操作都使用信號處理晦款,因此不需要做輪詢)。盡管如此,有些情況還是不能滿足要求颖榜,例如,每幀需要處理數(shù)以萬計(jì)的實(shí)體可能會達(dá)到性能瓶頸看铆。
Godot最有趣的設(shè)計(jì)決策之一是整個場景系統(tǒng)是可選的法瑟。雖然目前還不能將其單獨(dú)提出來,但是在運(yùn)行時(shí)可以完全繞過它浮庐。
與Unity類比甚负,就像是繞過Renderer組件柬焕,直接調(diào)用底層API渲染
在核心部分,Godot 使用了服務(wù)層的概念梭域。它們是用于控制渲染斑举、物理、聲音等的非常底層的 API病涨。場景系統(tǒng)是建立在他們之上富玷,并直接使用他們。最常見的服務(wù)層有:
- RenderingServer: 處理圖形相關(guān)
- PhysicsServer3D: 處理3D物理相關(guān)
- PhysicsServer2D: 處理2D物理相關(guān)
- AudioServer: 處理音頻相關(guān)
查看它們的API可以發(fā)現(xiàn)既穆,提供的函數(shù)都是Godot允許你做的所有事情的底層實(shí)現(xiàn)赎懦。
使用服務(wù)層的關(guān)鍵是理解資源 ID (RID)對象。這些是服務(wù)器實(shí)現(xiàn)的不透明句柄幻工。它們是手動分配和釋放的励两。服務(wù)器中的幾乎每個函數(shù)都需要 RID 來訪問實(shí)際資源。
大多數(shù) Godot 節(jié)點(diǎn)和資源都在內(nèi)部包含了來自服務(wù)層的這些 RID囊颅,可以通過不同的函數(shù)獲得它們当悔。事實(shí)上,繼承 Resource 的任何內(nèi)容都可以直接強(qiáng)制轉(zhuǎn)換為 RID迁酸,然后可以將資源作為 RID 傳遞給服務(wù)層 API先鱼。但是,并非所有資源都包含 RID(在這種情況下奸鬓,RID 將是空的)焙畔。
下面是一些使用Servers API的示例:
創(chuàng)建Sprite
extends Node2D
# RenderingServer需要維持一個紋理引用
var texture
func _ready():
# 創(chuàng)建一個CanvasItem
var ci_rid = RenderingServer.canvas_item_create()
# 設(shè)置當(dāng)前節(jié)點(diǎn)為父節(jié)點(diǎn)
RenderingServer.canvas_item_set_parent(ci_rid, get_canvas_item())
# 將紋理畫在CanvasItem上
texture = load("res://my_texture.png")
# 使用RenderingServer添加到渲染
RenderingServer.canvas_item_add_texture_rect(ci_rid, Rect2(texture.get_size() / 2, texture.get_size()), texture)
# 旋轉(zhuǎn)45°,變換位置
var xform = Transform2D().rotated(deg_to_rad(45)).translated(Vector2(20, 30))
RenderingServer.canvas_item_set_transform(ci_rid, xform)
Canvas Item API允許你往Canvas上畫東西串远,一旦添加宏多,它們便無法修改,需要清除然后再次添加(變換位置澡罚、旋轉(zhuǎn)不需要清除)伸但。
通過這個函數(shù)來清除:
RenderingServer.canvas_item_clear(ci_rid)
用過SFML可能會對這種方式比較熟悉(但又有一些不同),SFML中幾乎每幀都需要調(diào)用draw和clear
創(chuàng)建Mesh
extends Node3D
# RenderingServer需要維持一個網(wǎng)格引用
var mesh
func _ready():
# 創(chuàng)建一個3D實(shí)例.
var instance = RenderingServer.instance_create()
# 設(shè)置scenario留搔,這樣這個實(shí)例才會出現(xiàn)當(dāng)前世界中
var scenario = get_world_3d().scenario
RenderingServer.instance_set_scenario(instance, scenario)
# 添加網(wǎng)格
mesh = load("res://mymesh.obj")
RenderingServer.instance_set_base(instance, mesh)
# 移動網(wǎng)格
var xform = Transform3D(Basis(), Vector3(20, 100, 0))
RenderingServer.instance_set_transform(instance, xform)