從零開(kāi)始獨(dú)立游戲開(kāi)發(fā)學(xué)習(xí)筆記(六十五)--Godot 學(xué)習(xí)筆記(一)--入門(mén)-結(jié)束

因?yàn)?unity 不行了,所以換 godot 了。這時(shí)候?qū)W Godot 和 blender 具有同樣光明的未來(lái)。
0. 尋找教程
第一步的學(xué)習(xí)比較重要,不然差教程養(yǎng)成的壞習(xí)慣會(huì)伴隨終身(不至于,但比較難改)。
先從官方教程找起:

很幸運(yùn),有 Getting Startted,一個(gè)有 Getting Started 的軟件是具有光明前途的軟件。
簡(jiǎn)單翻一下看看,文檔用的是最新的 4.1 版本,正是時(shí)下的最新版,更新很快,一個(gè)文檔更新很快的軟件是具有光明前途的軟件(至少短時(shí)間內(nèi)是)。
我直接下載最新版本的 Godot,沒(méi)有選 .NET 支持,看了下別的地方的說(shuō)辭,說(shuō)是在網(wǎng)上搜索 Godot 問(wèn)題,大部分給的都是 GDScript 的回答,也就是說(shuō)選 C# 短時(shí)間內(nèi)沒(méi)有前途,所以直接 GDScript 走起,說(shuō)是很像 python,那正好上手簡(jiǎn)單。
開(kāi)學(xué)
1. 介紹
1.1 介紹 Godot
Godot 是一款能做 2D 和 3D 游戲應(yīng)用的引擎,也能做主機(jī)游戲。不過(guò)因?yàn)樵S可證問(wèn)題,Godot 不能原生支持主機(jī)游戲。不過(guò)沒(méi)關(guān)系,主機(jī)移植放哪里都是難事,Unity 也不簡(jiǎn)單,不是 Godot 的錯(cuò)。
1.1.1 Godot 能做啥
原來(lái) Godot 桑不是一開(kāi)始就是開(kāi)源的,是 2014 年才開(kāi)始開(kāi)源的,也就是這個(gè)時(shí)候軟件完全被重寫(xiě)并提高的。 然后展示了一堆 Godot 開(kāi)發(fā)的游戲,全是 2D 的。
1.1.2 Godot 看起來(lái)咋樣
自己下載看不就知道了,設(shè)計(jì)軟件還能咋樣,不都一樣嗎。

當(dāng)然 Godot 也可以連外部軟件,比如用 blender 畫(huà) 3D,或者 vscode,vs 來(lái)寫(xiě)代碼。
1.1.3 學(xué) Godot 前需要知道的事
你最好會(huì)代碼,Godot 沒(méi)有那么對(duì)代碼小白友好。
1.2 Godot 關(guān)鍵詞一覽
在 Godot 里,游戲場(chǎng)景 Scene 就是一棵有很多節(jié)點(diǎn)的樹(shù),樹(shù)上有許多節(jié)點(diǎn),你將把這些節(jié)點(diǎn)連在一起,讓他們之間發(fā)出和接受信號(hào)來(lái)互相連接。
1.2.1 場(chǎng)景 Scene
在 Godot 里,Scene 場(chǎng)景和別的游戲引擎里有稍微一點(diǎn)點(diǎn)的區(qū)別。Godot 里的引擎可以是一個(gè)大場(chǎng)景,也可以是一個(gè)房子,一塊石頭,一個(gè)角色,甚至是一個(gè) UI 界面。也就是說(shuō),Godot 里的 Scene,同時(shí)兼具其他引擎里 Scene 和 Prefabs 的功能,是一個(gè)很大的概念。
1.2.2 節(jié)點(diǎn) Nodes
節(jié)點(diǎn)是樹(shù)上的最小單位。
實(shí)際上,節(jié)點(diǎn)和場(chǎng)景(樹(shù))其實(shí)很像,一顆樹(shù)本身也可以嵌套進(jìn)另一顆樹(shù)上,這顆樹(shù)本身就成為了節(jié)點(diǎn)。
1.2.3 場(chǎng)景樹(shù) Scene tree
所有的場(chǎng)景會(huì)匯總在一棵場(chǎng)景樹(shù)上。場(chǎng)景這個(gè)時(shí)候又可以看作是節(jié)點(diǎn)了。所以其實(shí)可以混用。不過(guò)最好還是用場(chǎng)景這個(gè)詞來(lái)描述比較好,因?yàn)橹罢f(shuō)過(guò)了,場(chǎng)景可以是任何東西。最好還是說(shuō)你的游戲由場(chǎng)景構(gòu)成。
1.2.4 信號(hào) Signal
節(jié)點(diǎn)可以發(fā)出信號(hào),借此可以讓節(jié)點(diǎn)間傳遞信息,這一特性可以讓你少編程一點(diǎn)。提供了一些靈活性。
(說(shuō)白了,其實(shí)就是觀察者模型在 Gogot 里的應(yīng)用罷了)
舉個(gè)例子,按下按鈕觸發(fā)一個(gè) Signal,然后用來(lái)執(zhí)行一段代碼或者某個(gè)其他事件之類(lèi)的。
包括還提供了一些類(lèi)似于物體碰撞的時(shí)候發(fā)出的信號(hào),這種常用功能。
1.2.5 總結(jié)
場(chǎng)景,節(jié)點(diǎn),場(chǎng)景樹(shù),信號(hào)是 Godot 的 4 個(gè)核心概念。
1.3 稍微看下 Godot 的界面
Godot 真的很輕量,官網(wǎng)直接下載一個(gè)壓縮包,解壓直接用就行。
建完項(xiàng)目看了下,其實(shí)就一很標(biāo)準(zhǔn)的設(shè)計(jì)工具。左上邊是場(chǎng)景樹(shù),左下是資產(chǎn)。右邊是當(dāng)前物體的屬性等。下面是各種調(diào)試程序動(dòng)畫(huà)面板之類(lèi)的東西。
中間的主屏幕有 4 個(gè)分類(lèi),2D,3D,Script,AssetLib。 2D 3D 就不說(shuō)了,開(kāi)發(fā)哪個(gè)就用哪個(gè)。Script 是 Godot 內(nèi)置的代碼編輯器,帶有基礎(chǔ)的功能。AssetLib 就是免費(fèi)素材商店罷了。
Godot 還內(nèi)置了 Class Reference 的文檔, 按 F1 呼出,或者在 Script 里按住啊 Ctrl + 點(diǎn)擊。便可方便地看文檔。有一種吊打 Unity 的美。

1.4 新 Features
其實(shí)就是教你咋學(xué) Godot,沒(méi)啥,給了一堆搜索技巧,一些編程學(xué)習(xí)網(wǎng)站。然后再給一些論壇,基操了。
看了下給的幾個(gè)論壇:
Reddit 很活躍,但基本都是吹水 “我花了 XX 小時(shí)做出的場(chǎng)景” 這種。以及一些 Feature Request。感覺(jué)比較適合有一定了解的人進(jìn)來(lái)玩玩看。
Godot 論壇,難以置信居然還挺活躍,但也沒(méi)那么活躍吧,只能說(shuō)和其他比起來(lái)算活躍的。
Discord, 毋庸置疑最活躍的地方,適合新人。
看到一個(gè) qq 群鏈接,死活打不開(kāi)。不過(guò)反正 qq 群正是技術(shù)漩渦,能不進(jìn)就別進(jìn)吧。
1.5 Godot 的設(shè)計(jì)
原文是設(shè)計(jì)哲學(xué),算對(duì)哲學(xué)這個(gè)詞的濫用吧。
1.5.1 面向?qū)ο笤O(shè)計(jì)
Godot 的核心是面向?qū)ο笤O(shè)計(jì),這個(gè)看之前樹(shù)節(jié)點(diǎn)啥的就能感覺(jué)到。Godot 想營(yíng)造出一種不同于傳統(tǒng)游戲編程的模式,更加直觀。
Godot 里你可以構(gòu)建和聚合場(chǎng)景,并嵌套。比如你可以創(chuàng)造一個(gè)燈場(chǎng)景,再創(chuàng)造一個(gè)路燈場(chǎng)景包含燈場(chǎng)景。再建一個(gè)道路場(chǎng)景上面有路燈場(chǎng)景,這個(gè)時(shí)候改變燈的顏色,所有路燈的顏色都會(huì)改變。
如果你對(duì) OOP 編程熟悉的話(huà),其實(shí)所謂的 Scene 就是 Class。
請(qǐng)注意,雖然聽(tīng)起來(lái)像是 Prefabs 的定義,但 Scene 不是 Prefabs,因?yàn)槟憧梢宰杂蓴U(kuò)展 class。修改基底 class 會(huì)同時(shí)更改高級(jí) class。讓在 Godot 里的游戲設(shè)計(jì)和 oop 息息相關(guān)。
1.5.2 nodes
Godot 提供了一系列的節(jié)點(diǎn)用作各種目的。節(jié)點(diǎn)是樹(shù)的一部分并由母節(jié)點(diǎn)繼承而來(lái)。Godot 里大多數(shù)節(jié)點(diǎn)是獨(dú)立存在的,除了某些像是碰撞節(jié)點(diǎn)是屬于物理實(shí)體的這種設(shè)定。

以上圖為例,一個(gè) Sprite2D 同時(shí)既是 Sprite2D,也是 Node2D,還是 CanvasItem,還是個(gè) Node,它繼承母節(jié)點(diǎn)的所有特性。
1.5.3 Godot 的目標(biāo)
Godot 意圖提供所有制作游戲的工具,不過(guò)顯然現(xiàn)在還做不到,所以提供了許多使用外部工具的途徑。
順帶一提,Godot 本身這個(gè)軟件就是一個(gè)使用 Godot 引擎制作的游戲。正如 Git 的代碼目前是用 Git 在管理一樣,所有成功的開(kāi)源軟件都這樣。這也是為什么這款引擎對(duì)編程具有一定的要求。
2. 一步一步做游戲
2.1 節(jié)點(diǎn)
之前只是粗略地說(shuō)了節(jié)點(diǎn),讓我們實(shí)際上引擎來(lái)看一下。
節(jié)點(diǎn)是執(zhí)行功能的最小模塊,Godot 內(nèi)置了許多節(jié)點(diǎn),執(zhí)行各種類(lèi)似攝像機(jī),UI,等各種功能。
一般來(lái)說(shuō),節(jié)點(diǎn)具有以下屬性:
名字。
可以更改的屬性。
每幀都有的 callback 函數(shù)。
可以擴(kuò)展更多的功能。
可以作為其他節(jié)點(diǎn)的子節(jié)點(diǎn)。
2.2 場(chǎng)景
當(dāng)把節(jié)點(diǎn)組成一棵樹(shù)的時(shí)候,這就是一個(gè)場(chǎng)景。場(chǎng)景本身又可以作為一個(gè)節(jié)點(diǎn)。
Godot 就是一個(gè)場(chǎng)景編輯器,你可以做很多場(chǎng)景,Godot 只需要一個(gè)場(chǎng)景作為主場(chǎng)景,用于第一次加載。
場(chǎng)景有以下的特性:
有一個(gè)根節(jié)點(diǎn)。
可以被儲(chǔ)存在硬盤(pán)上以供以后加載。
場(chǎng)景可以被實(shí)例化,比如有一個(gè)人物場(chǎng)景,然后根據(jù)此實(shí)例化十幾個(gè)人物。
實(shí)際操作
不難,加了個(gè) Label ,很容易就 Hello world 了。

Scene 在實(shí)例化之后,如果改變?cè)?Scene,所有的 instance 都會(huì)改變。當(dāng)然也可以直接改變某個(gè) instance 的屬性,這個(gè)屬性會(huì)與原始 Scene 失去聯(lián)系,從此改變?cè)?Scene 的這個(gè)屬性,再也不會(huì)影響這個(gè) instance,除非點(diǎn)擊屬性旁邊的還原按鈕。
整個(gè) Godot 都是以 Scene 和實(shí)例化來(lái)建立的。當(dāng)使用 Godot 創(chuàng)作游戲的時(shí)候,不需要考慮什么設(shè)計(jì)模式。直接從玩家看到的對(duì)象開(kāi)始設(shè)計(jì)和想象。
2.3 GDScript
腳本要掛在 node 上,是 node 的擴(kuò)展和延伸。也就是說(shuō)腳本擁有這個(gè) node 的所有屬性。腳本用于 Godot 本身無(wú)法完成的功能。
Godot 本身支持多語(yǔ)言,甚至可以在一個(gè)項(xiàng)目里使用多種語(yǔ)言,比如用 GDScript 寫(xiě)簡(jiǎn)單的 gameplay,用 C# 寫(xiě)復(fù)雜的算法來(lái)優(yōu)化性能。
GDSCript 是 Godot 自家的語(yǔ)言,當(dāng)然和引擎本身具有最緊密的聯(lián)系。
2.4 創(chuàng)建第一個(gè)腳本
每個(gè) GDScript 腳本本身就是一個(gè) class,比如在 Godot 里添加腳本自帶的 extends Node,意思就是母節(jié)點(diǎn)是 Node。
在 Inspector 里看到的所有屬性都是以空格形式隔開(kāi),在代碼里這些屬性就是全小寫(xiě)并把空格換成下劃線(xiàn)即可得出,鼠標(biāo)移動(dòng)到屬性上可以看到詳情。
_init() 函數(shù)是初始化的時(shí)候執(zhí)行,_process(delta)則是每幀執(zhí)行,其中 delta 是上一幀到現(xiàn)在的時(shí)間。
所有內(nèi)置方法都是以 _ 開(kāi)頭以區(qū)分。
最后寫(xiě)了這樣的代碼來(lái)控制物體旋轉(zhuǎn):
js
func _process(delta): ? ?rotation += delta * angular_speed ? ?var velocity = Vector2.UP.rotated(rotation) * speed ? ?position += velocity * delta
解讀:每幀執(zhí)行,改變旋轉(zhuǎn)角度。Vector2.Up的目的是創(chuàng)造角度,先創(chuàng)造一個(gè)向上的方向(也就是 0 度),再旋轉(zhuǎn) rotation 大小的角度。所以綜上就是創(chuàng)造一個(gè)跟隨者旋轉(zhuǎn)角度的速度向量賦予給 position,所以整體物體就會(huì)繞著中心旋轉(zhuǎn)。
2.5 監(jiān)聽(tīng)用戶(hù)輸入
Godot 里有兩種方式監(jiān)聽(tīng)用戶(hù)輸入:
和 _process 類(lèi)似,Godot 也提供了 _unhandled_input 函數(shù)來(lái)處理鍵盤(pán)輸入,這個(gè)函數(shù)會(huì)在每次用戶(hù)按鍵的時(shí)候觸發(fā)。
Input Singleton。Singleton 是一個(gè)全局變量,可以在任何地方訪(fǎng)問(wèn),Godot 提供了很多 Singleton 來(lái)做各種事情。
這里我們使用第二種方法:
js
func _process(delta): ? ?var direction = 0 ? ?if Input.is_action_pressed("ui_left"): ? ? ? ? ? ?direction = -1 ? ?elif Input.is_action_pressed("ui_right"): ? ? ? ? ? ?direction = 1 ? ?rotation += delta * angular_speed * direction ? ?var velocity = Vector2.ZERO ? ?if Input.is_action_pressed("ui_up"): ? ? ? ? ? ?velocity = Vector2.UP.rotated(rotation) * speed ? ?position += velocity * delta
2.6 使用 Signal
Signal 是 node 之間傳遞信息的方式,可以讓某個(gè)事件觸發(fā) signal,也能讓 signal 觸發(fā)某個(gè)事件。
Signal 是 Godot 實(shí)現(xiàn)的委托機(jī)制。可以讓兩個(gè)對(duì)象在不引用對(duì)方的情況下做出反應(yīng)。
比如說(shuō)生命值條會(huì)在玩家受傷后縮短。
信號(hào)其實(shí)就是 Godot 的觀察者模式的實(shí)現(xiàn)。
2.6.1 在編輯器里使用信號(hào)
比如我們想做一個(gè)按鈕,按鈕按下觸發(fā)一個(gè)信號(hào)來(lái)觸發(fā)某段代碼執(zhí)行。想要信號(hào)起作用,信號(hào)要連接到一個(gè)具有腳本的節(jié)點(diǎn),并鏈接一個(gè)接受函數(shù),信號(hào)觸發(fā)后就會(huì)執(zhí)行這個(gè)函數(shù),正好我們之前寫(xiě)了一個(gè)腳本。按鈕信號(hào)正好可以傳到這個(gè)節(jié)點(diǎn)上并執(zhí)行其腳本里的接受函數(shù)。
當(dāng)然,信號(hào)也可以連接到內(nèi)置函數(shù)上,這算高級(jí)內(nèi)容了。
使用 Godot 內(nèi)置信號(hào)功能后,會(huì)在接受節(jié)點(diǎn)的腳本上創(chuàng)建對(duì)應(yīng)的接受函數(shù)。
2.6.2 在代碼里使用信號(hào)
雖然編輯器里也可以使用,但是如果節(jié)點(diǎn)是在游戲過(guò)程中生成的,那就沒(méi)辦法用了。
在代碼里操作也不難,找到節(jié)點(diǎn)再與對(duì)應(yīng)的信號(hào)connect就行了。注意是與信號(hào)connect而不是節(jié)點(diǎn)。
2.6.3 自定義信號(hào)
當(dāng)然,開(kāi)發(fā)者也可以自定義信號(hào)。
文檔居然提到了命名規(guī)則,信號(hào)最好用動(dòng)詞的過(guò)去式來(lái)命名。
定義好信號(hào)后,使用 emit 來(lái)觸發(fā)信號(hào)。其他的和普通信號(hào)一樣。會(huì)去執(zhí)行連接的腳本。
3. 第一個(gè) 2D 游戲
Godot 官方上來(lái)就是直接教做手游,光明的未來(lái)(
3.1 基礎(chǔ)項(xiàng)目設(shè)置
因?yàn)槭鞘钟?,所以?duì)寬高比要有不一樣的設(shè)置,去往項(xiàng)目設(shè)置里設(shè)置為 480 x 720。
不過(guò)這樣不夠自適應(yīng),所以我們還要再拉伸力設(shè)置模式為 canvas_item 并讓 aspect 設(shè)置為 Keep。自適應(yīng)設(shè)置參考這里。
3.2 玩家 Scene
先從創(chuàng)建玩家 Scene 開(kāi)始,根節(jié)點(diǎn)選擇 Area2D,這么選的原因是玩家需要判斷碰撞,哪些東西進(jìn)入了玩家區(qū)域。這是“玩家”的基本功能,而一個(gè)規(guī)則就是,一個(gè) Scene 的根節(jié)點(diǎn)應(yīng)該要反應(yīng)這個(gè) Scene 的基本功能特性,然后再加各種子節(jié)點(diǎn)來(lái)增加功能。
3.2.1 編組防止手賤
在添加子節(jié)點(diǎn)之前,為了確保我們不手賤拖拽了子節(jié)點(diǎn)或者變大縮小。將其編組:

3.2.2 命名規(guī)范
Class 采用 PaselCase,函數(shù)變量都用 snake_case,常量全大寫(xiě)。(還說(shuō)自己不是python
3.2.3 2D 動(dòng)畫(huà)
創(chuàng)建一個(gè)子節(jié)點(diǎn)-》AnimatedSprite2D。有一個(gè) waning,告訴我們?nèi)鄙?Frame,畢竟是動(dòng)畫(huà)。正好素材里有,我們直接加。
選擇創(chuàng)建 SpriteFrame,點(diǎn)擊后下面出現(xiàn)面板。給左邊的 Default 重命名為自己想要的。按照教程添加好兩個(gè)動(dòng)畫(huà)。
3.2.4 碰撞
添加一個(gè) collisionShape2D,shape里創(chuàng)建一個(gè)形狀。
3.3 Player 代碼
@export 可以讓參數(shù)暴露在 inspector 里。
3.3.1 輸入映射
輸入配置在項(xiàng)目設(shè)置里,配置好四個(gè)方向的移動(dòng)后,代碼里就可以用 Input.is_action_pressed 來(lái)獲取剛剛配置的輸入了。
代碼如下:
js
extends Area2D@export var speed = 1600var screen_size# Called when the node enters the scene tree for the first time.func _ready(): ? ?screen_size = get_viewport_rect().size# Called every frame. 'delta' is the elapsed time since the previous frame.func _process(delta): ? ?var velocity = Vector2.ZERO ? ?if Input.is_action_pressed("move_left"): ? ? ? ?velocity.x -= 1 ? ?if Input.is_action_pressed("move_right"): ? ? ? ?velocity.x += 1 ? ?if Input.is_action_pressed("move_up"): ? ? ? ?velocity.y -= 1 ? ?if Input.is_action_pressed("move_down"): ? ? ? ?velocity.y += 1 ? ?if velocity.length() != 0: ? ? ? ?velocity = velocity.normalized() ? ? ? ?$AnimatedSprite2D.play() ? ?else: ? ? ? ?$AnimatedSprite2D.stop() ? ?position += velocity * speed * delta ? ?position = position.clamp(Vector2.ZERO, screen_size)
其中,$ 代表 get_node 的簡(jiǎn)寫(xiě),路徑為相對(duì)當(dāng)前節(jié)點(diǎn)。所以能獲取子節(jié)點(diǎn)。
Clamp則為限制數(shù)值不能超過(guò)的最小值最大值,這里設(shè)置為不能超過(guò) screen size。
3.3.2 碰撞
碰撞使用 Area2D 的 bodyenter 信號(hào)。信號(hào)里寫(xiě)上如下代碼:
js
func _on_body_entered(body): ? ?hide() ? ?$CollisionShape2D.set_deferred("disabled", true)
使用 set_deffered 而不是只直接$CollisionShape2D.disabled= true,因?yàn)橹苯釉O(shè)置的話(huà),如果這幀還在繼續(xù),那么會(huì)出現(xiàn)錯(cuò)誤。要等這一幀結(jié)束再設(shè)置才比較好。
3.4 敵人
教程這里就很粗糙了,完全都不解釋就直接讓你加一堆節(jié)點(diǎn)。
不過(guò)看到最后還是能理解的,整體來(lái)講沒(méi)什么新東西,新的只有節(jié)點(diǎn)的作用,但基礎(chǔ)操作方法和 player 沒(méi)有變化。
3.5 主要場(chǎng)景
玩家和敵人創(chuàng)建好后,接下來(lái)就是創(chuàng)建主要游戲場(chǎng)景了。
根節(jié)點(diǎn)用 Node(不是 Node2D)因?yàn)橹饕獔?chǎng)景的功能并非2d特有。接下來(lái)我們實(shí)例化玩家和敵人。
3.5.1 path
path節(jié)點(diǎn)可以用來(lái)設(shè)置敵人安放的路徑。 創(chuàng)建好 path 后,添加一個(gè) pathfollow2d 節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)會(huì)在 path 上巡邏,然后我們?cè)僭谶@個(gè)節(jié)點(diǎn)上放置敵人,這樣敵人就會(huì)在 path 上安放(所以剛才的path最好不要放在屏幕中間,不然敵人會(huì)憑空出現(xiàn))。
3.5.5 實(shí)例化敵人
我們?cè)?script 里 export 一個(gè) PackedScene 格式的變量,然后把敵人scene賦值給它。這里教程真的很爛,就一股腦告訴你咋做咋做,也不告訴你為啥要這么做,總之就是很爛。雖然確實(shí)是該教的都教了,但就是很爛。
3.6 UI
主要場(chǎng)景能玩后做 UI。
選用 CanvasLayer 作為節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)會(huì)出現(xiàn)在其他節(jié)點(diǎn)上面,正是 HUD 所需。 剩下的按照教程來(lái)就行了,基本邏輯都不變,節(jié)點(diǎn),場(chǎng)景,信號(hào)基本特性熟了后,剩下的只不過(guò)是一些 best practice 的熟悉罷了。況且這教程代碼看著一點(diǎn)也不 best。所以就不講了。
3.7 額外潤(rùn)色
Input Mapping 里添加的按鍵還可以用于創(chuàng)建快捷鍵。比如我們創(chuàng)建一個(gè)叫做 start_game的input。然后在按鈕上綁定,就可以讓按鍵操作按鈕了。
4. 成果
一個(gè)小垃圾游戲。不過(guò) Godot 確實(shí)好學(xué),方便簡(jiǎn)單,喜歡。