Godot入門到棄坑:GDScript精要 日積月累 循環往復


3樓貓 發佈時間:2024-01-31 13:32:41 作者:cameLcAsE Language

先來學個重要的小把戲。
我要怎麼在代碼中引用某個節點呢?——為什麼要去引用一個節點呢?因為在一個複雜的場景中,我們可能需要通過代碼控制場景中的某個節點。
告訴你一個簡單粗暴的辦法:
就這麼簡單,我們就在代碼中拿到了這個logo。這是GDScript專門為節點設計的一種特殊語法,可以直接通過類似於文件路徑的語法($Path/To/The/Node)來獲得場景中的某個節點。場景和文件系統一樣呈現樹形結構,這種路徑式語法是很符合直覺的。例如在另一個更復雜的場景中:
要獲得Background,其路徑為$MarginContainer/VBoxContainer/Content/Background。
不過,這樣子只能在_ready中引用它,很麻煩。好吧,那你說在外面定義它吧。
然後報錯了。錯誤信息說:
The default value is using "$" which won't return nodes in the scene tree before "_ready()" is called. Use the "@onready" annotation to solve this.
在ready調用之前,使用$的默認值不會返回場景樹中的節點。用@onready解決此問題。
簡單來說,ready還沒調用的時候場景肯定還沒準備好。那麼場景中的相關節點肯定也沒準備好,如果此時嘗試引用場景中的某個節點,我們可能拿不到一個正常的節點,它甚至可能是空的。
前面已經講到了@export標記,這裡要用到另一個標記@onready。和它字面上的意思一樣,被@onready標記的變量會延遲到ready被調用後再進行賦值,此時場景已經準備好了,引用某個節點也不是問題。
現在問題來了。如果場景中有5個logo排成一排,我想讓它們每幀逐個上升一點怎麼辦?
先準備一個場景,在一個2D場景中放入若干個Sprite2D。如果你跟著前面的文章做了,用那個有很多logo的場景也行。
大概這樣

大概這樣

你可能想到用剛才所學,直接定義5個@onready變量挨個操作。不錯,那要是有10個logo,甚至100個呢?
首先要解決的問題是,如何引用這100個logo。
前面介紹到的數據類型,都是代表某一個東西。這裡我們要引用很多個東西,就需要用到一些容器(container)來裝它們。在某些時候,這類容器也被稱為集合(collection,不要和源自數學的集合set混淆)。

數組

數組(array)是我們要學習的第一種容器類型。GDScript中的數組用以保存一系列對象。數組中的內容可以是任何類型,並且數組可以動態增減其中的元素。
要獲得數組中的元素,也需要用到方括號。方括號中填入元素所在位置。這個位置我們一般稱為索引(index)。有時候也稱為下標(subscript):
注意,在GDScript以及大部分主流編程語言中,數組的索引都是從0開始的。所以第一個元素的索引為0,1是第二個。
GDScript中的數組索引和Python一樣,可以為負數。-1就是數組的最後一個元素,-2則是倒數第二個:
下面是一些常用的用來操作數組的函數:
append會在數組末尾追加一個元素。pop_back會彈出數組的最後一個元素並返回,而`pop_at`則是彈出指定位置的元素。
數組的`push_back`函數和append作用相同。pop和push實際上是針對棧(stack)這種數據結構常用的術語。概念上來講,棧是一種操作受限的數組。它只能在最後(或者說”上面“)追加元素,每次只能刪除最後(最上面)一個元素。是一種先進後出/後進先出(Last in first out)的數據結構。想象一下(沒有”托盤“的)筒裝薯片。
如果我要你逐個print數組中的元素,(中略),那要是你有一萬個元素在數組中呢?
這裡我們需要用到循環(loop)來減少重複的代碼。

當…

為了輸出數組中的諸元素,我們可以用一個變量來記錄當前print到哪裡了。只要這個值還沒超過數組的長度——準確地說是“最大有效索引”——我們就繼續print。
這裡可以到while循環。while循環的語法如下:
只要while後面的條件滿足(也就是說為真),while下方的代碼塊就會執行,否則就退出循環。
len函數顧名思義(length),可以求出數組的長度。
這個變量初始化為0,也就是數組的一個有效索引。在條件中,只要它不大於數組長度減一,那就說明還沒有print到頭。
在循環內部,print之後我們給i加一,下次循環就會print下一個了。如果忘了這一句,你的循環就會永無止境地繼續下去……

對於…

好吧我又一次騙了你。其實上面的循環這樣寫太複雜了。對於數組這樣的集合類型,還有一種更簡單的循環方式,這就是for in循環。
for後面是一個變量名,這個變量會在每一次循環中拿到數組中的一個元素,隨後就可以在循環代碼塊中引用它。

字符串也像數組一樣

實際上字符串也可以這樣用:
在某些場合我們稱這類數據結構為可迭代(iterable)對象,也就是說它們可以逐個產生出一系列對象,且通常可以和編程語言中的某些語法進行互動以簡化代碼。我們會在後續介紹更多的可迭代對象。

控制循環

除了逐個處理可迭代對象或者根據條件循環,我們也可以在循環內部按照額外的條件來控制循環。
break語句可以直接結束循環。比如這裡到3就直接結束循環:
到3為止且不會輸出3

到3為止且不會輸出3

continue會跳過本次循環的剩餘代碼,直接進入下一次循環。如果我們只想在元素為偶數時進行處理,我們在發現不是偶數時就進入下一次迭代。
這裡出現了一個沒介紹的算術運算符%,它是取模運算符,或者更直白地說叫求餘數運算符。如果一個數無法被2整除那麼它就是奇數。
現在還需要最後一塊拼圖來完成一開始的問題。我們如何獲得場景中所有的Sprite2D而不用挨個創建變量呢?
這裡用到一個叫find_children的函數。顧名思義它可以找到腳本所在節點的孩子節點。它的第一個參數是節點路徑,第二個可選參數是節點類型。在這裡我們想要找到所有的Sprite2D,所以第一個參數留一個通配符就好,第二個參數填入Sprite2D。注意是字符串,可以在文檔中查到該函數的詳細信息。
這個函數會返回一個數組,這正是我們需要的。
接下來就是在process裡實現我們的目標。
很簡單,直接修改position的y座標即可。
有了循環和數組的幫助,就算它有一萬個也不怕!

是否在…

在上面的for in循環中出現了in關鍵字,實際上它還有一個更符合直覺的用途,就是檢查某個東西是否存在於數組(以及其他集合中):
由此可知,in作為一個運算符,用到它的表達式也會返回一個布爾值。

字典

字典是GDScript中的另一個內置集合類型。它的作用是表達某種映射關係。在不同的編程語言中,類似的類型有不同的名字,但是作用是類似的。
好吧,那啥是映射(map)。一般來說映射是一個把兩個集合(set)聯繫起來的函數,通過這個函數可以通過一個集合中的某元素得到另一個集合中的某元素。例如現實中的字典,是字到含義的映射。身份證號碼和公民之間也存在映射關係。
在編程語言中,字典通常被描述為鍵值對(key-value pair)的集合。鍵值對就是由鍵和值組成的一種簡單的數據結構。字典描述的就是鍵的集合到值的集合的映射。編程語言中的字典通常一個鍵對應一個值,不過多個鍵可以對應同樣的值。在數學上可以用滿射(surjection)稱呼。
GDScript中可以用專門的字典字面量來構造字典:
這裡展示的是遊戲名稱和其metacritic評分之間的對應關係。可以看到,字典字面量通過花括號來構造。每個元素(鍵值對)和數組一樣用逗號隔開。鍵值對中鍵和值用冒號隔開。這裡為了便於展示將它們換行書寫。
和數組類似,我們用方括號獲得字典中的元素——實際上是根據鍵來獲得對應的值:
如果試圖用一個不存在的鍵訪問字典,運行時會直接報錯。in對於字典同樣可用,它會檢查是否存在於字典中:
字典也可以用在循環中:
在for in循環中拿到的是字典的鍵。如果想要獲得字典的所有的值,可以用它的values函數來獲得:
可以看到,字典的鍵可以是字符串類型。實際上GDScript中的字典的鍵支持很多類型,並且同一個字典中的鍵和值的類型也可以各不相同:
數組一類的相對連續的數據結構可以根據元素所在位置來求得其所在(內存)位置,因此我們也就可以根據索引按順序獲得元素。而字典通常用某種哈希算法實現,它根據鍵的值通過某種方式計算出值所在的位置。因此使用字典時往往暗示我們不關心順序。
這篇文章中我們又學到了一個編程中的重要概念:循環。同時還學習了可以在搭配循環使用的兩種數據結構。GDScript這條惡龍已經越來越虛弱了!我們越來越接近勝利了!


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