語言教學
內容
•介紹
• BGT 如何運作？
•遊戲創作過程
• BGT 引擎
• BGT 編譯器
•語言文法
•聲明
•區塊
•表達
•評論
•在螢幕上列印文字
•變數
•變數；這些是什麼？
•宣告和分配變數
•積分變數與壞桃子
•浮點數變數
•字串變數
•常量
•概括
•功能
•條件語句
• If 語句
•開關和外殼
•循環
• While 循環
•執行 while 迴圈
• For 循環
•中斷並繼續
•陣列和字典
•物件和類別
•高階物件技術
•物件簡介
•繼承
•介面
•運算子重載
•函數句柄
•使用多個腳本
•最後的說明

1. 簡介
歡迎來到 BGT 語言教學！本教程假設您對 BGT 一無所知，但您有足夠的動力坐下來學習。本教程將涵蓋語言從最基礎到最高級的所有內容，我的目的是讓您在閱讀完本教程後能夠直接深入了解函數參考和示例遊戲，當然，也可以在適當的時候開始自己構建東西。所以，把腳放在桌子上，放鬆，準備好…BGT！
2. BGT 如何運作？
BGT 本質上包含兩個元件，一個用於運行你的遊戲，另一個用於將你的遊戲轉變為你的最終用戶可以使用的單獨程式。我們將更詳細地介紹這些組件。但是，首先您一定想知道，我該如何創作遊戲呢？
2.1.遊戲創作過程
BGT 引擎最初是一個空白軟體，具有音訊遊戲的核心功能，例如操縱聲音、計時器、檔案和檢查使用者輸入。 BGT 與通用程式語言編寫的音訊遊戲之間的差異在於，BGT 會讀取您編寫的一個或多個檔案。這些文件只是指示 BGT 要做什麼。例如，您可以播放徽標聲音，同時檢查按鍵以中斷它，然後建立選單。
這一切都是透過編寫腳本來完成的。這些都是使用一組特殊的規則（稱為語言語法）編寫的。這有助於 BGT 準確了解您希望它執行的任務。
我們將在另一部分介紹這些規則。首先，請容許我向您介紹一下 BGT！
2.2. BGT 引擎
BGT 是核心組件。這是讀取腳本並執行您所概述的任務的程式。提供此功能是為了讓您能夠輕鬆測試您的遊戲。如果沒有這個，您將無法注意到任何問題或錯誤，直到完成遊戲為止。
2.3. BGT 編譯器
雖然 BGT 引擎實際上會打開腳本並立即運行，但編譯器用於完成腳本。雖然編譯器的功能不是所有人都能立即辨識的，但它確實具有非常重要的功能。作為遊戲的創造者，只有您和其他像您一樣的人才能在他們的機器上安裝 BGT 引擎。確實，理論上你可以重新分配引擎和 BGT 腳本，但這並不總是合適的。例如，有時您的腳本包含私人登入訊息，用於存取聲音或伺服器，甚至允許其他使用者更改您正在開發的遊戲。為了防止這種情況，我們使用 BGT 編譯器。這將獲取腳本檔案並將其轉換為可分發的可執行程式。
您的最終用戶可以簡單地在他們的機器上運行該程序，而不必擔心幕後發生的一切。
3. 語言文法
好啦，閒話說夠多了，我們開始吧。
在開始任何程式設計之前我必須提出一些觀點：
3.1.聲明
語句只是 BGT 將遵循的一條指令。每個語句以分號字元分隔。如果您願意的話，您也可以用換行符將其進一步分隔。
3.2.區塊
為了讓您的程式碼對您和 BGT 引擎更具可讀性，我們使用程式碼區塊將一種情況與另一種情況分開。這些只是一組用特殊字元括起來的語句。如果正在檢查狀態訊息，例如聲音是否正在播放、計時器是否已到達某個時間點，我們可能想要做不同的事情。如果我們想一遍又一遍地重複一段程式碼，我們必須告訴 BGT 何時停止檢查或重複。為了做到這一點，我們使用塊。
程式碼區塊用括號括起來，也就是 { 和 } 字元。
3.3.表達式
表達式通常是特殊用途的語句，通常是程式碼區塊的起點。表達式通常由關鍵字和表達式代碼組成，並用括號括起來。關鍵字是諸如 if、while 和 do 之類的詞，用於告訴引擎執行某些特定的操作。對於 if 來說，檢查給定的表達式是否為真；對於 while 和 do 來說，要求編譯器一遍又一遍地重複程式碼區塊，直到滿足表達式條件為止。本教學後面將介紹條件語句和重複程式碼區塊的操作。
這裡必須提到，表達式不包含分號，因為我們不是給引擎直接指令，而是給執行指令的條件。
3.4.評論
當您編寫程式碼時，當然要記住，程式碼之所以被稱為程式碼，是因為它具有從英文變為符號，甚至在未來幾年變得徹頭徹尾令人困惑的神奇能力，添加註釋總是很好的做法，無論是解釋可能令人困惑的部分，還是添加作者、日期、修訂等資訊。新增註解的過程很簡單。
評論可以是任何內容。它不必符合任何規則，只需符合您自己的規則。您可能會寫簡單的英語，您可能會寫一行您嘗試過的錯誤代碼並決定稍後找出錯誤，您可能會編寫自己的神秘代碼以阻止其他人理解您的筆記。無論哪種方式，都由你決定。它可以短至一個字符，也可以長至聖經，只要您的電腦有足夠的磁碟空間來保存檔案並有足夠的內部記憶體來讀取它。
若要新增簡短的評論，請使用 //（兩個正斜線符號）來通知 BGT 忽略該行上的所有剩餘文字。您可以寫一條評論告訴引擎您有多討厭它，而它不會眨一下眼睛，部分原因是它根本就無須眨一下眼睛，但更重要的是，因為它會完全跳過該部分，直到下一行的開始。
若要新增長註釋，請使用 /*（斜線後跟星號）。這被稱為塊註釋。如果你願意的話，你可以在這裡盡情玩耍並將聖經貼在代碼前面。當您完成講道、咆哮、筆記、介紹性資訊或通訊錄時，您以 */（星號後跟斜線）結束。 BGT 將跳過區塊註解符號中包含的任何文字。
4. 在螢幕上列印文字
學習新語言時，通常首先要做的事情之一就是在螢幕上列印一些文字。它盡可能簡單，但同樣地，它被證明是一種熟悉語言語法和操作風格的非常有效的方法。說夠了，我們開始列印吧！

空主（）
{
alert("你好", "我是 BGT 腳本！");
}

上面的腳本乍看之下可能有些令人困惑，但實際上並沒有那麼糟糕。讓我們一點一點剖析一下。

空主（）

這告訴編譯器我們正在使用 main 函數，它是腳本啟動時執行的第一個程式碼區塊。我們稍後會介紹函數，但在編寫腳本時務必記住要建立此函數。

{

您可能還記得上一節說過，程式碼區塊總是用括號括起來。函數就是其中一種情況。

警報（

這是 BGT 引擎提供的功能名稱，它在螢幕上顯示一條帶有標題和一些文字的簡單訊息。 alert 函數有兩個參數，第一個是標題，第二個是要顯示的訊息。呼叫函數時，總是在左括號和右括號之間指定其參數（如果有），並用逗號分隔參數。

“你好”，

這是給予警報函數的第一個參數，即作為訊息標題的一段文字。

「我是BGT腳本！」）；

這是引號內寫的另一段文本，是警報函數的第二個參數，它指定要顯示的實際文本。這也是函數的最後一個參數，可以透過右括號看到，表示函數呼叫的結束。括號後的分號表示語句的結束。

}

這告訴編譯器我們現在已經完成了主要功能。

當寫出這樣的文字時，你總是必須用引號將其括起來。這是為了讓腳本解釋器更容易操作，否則它可能會將您的文字與實際的 BGT 程式碼混淆。

再看一遍腳本，並記住剛剛讀過的解釋。

空主（）
{
alert("你好", "我是 BGT 腳本！");
}

希望現在事情會更清楚一些。如果您仍然不明白，那麼在繼續閱讀之前，請先將以上段落再讀幾遍，以確保您掌握了基本概念。
5.變數
5.1.變數；這些是什麼？
如果你在數學課上夠清醒，掌握了方程式的基本知識，理解變數就是小菜一碟。但是對於那些在玩的過程中睡著了或像我一樣覺得躲在教室後面玩劊子手遊戲更有趣的人，這裡有一個簡單的解釋，可以解開這個謎團。

簡單地說，變數就像是裝有東西的籃子。變數可以告訴您動作遊戲中玩家的健康狀況、魚的當前價格或生命的意義是 42。基本上它可以包含您賦予它的任何值，並且您可以隨意檢索和擺弄該值。 BGT 支援五種基本類型的變量，我們將在本章中嘗試其中兩種。支援的類型有整數變數、浮點變數、字串變數、布林變數和物件變數。但在我們繼續了解所有這些不同類型之前，我們必須介紹一件事。
5.2.宣告和分配變數
要設定變量，首先必須告訴 BGT 您要使用哪種類型的變量，然後告訴您下次更改或讀取變量時將用來引用變量的名稱。
所有變數都必須有一個名稱。沒有例外。變數名可以包含從 a 到 z 的字母（包括大寫字母）、數字 1 到 0 以及底線。但是，變數的名稱絕不能以數字開頭，儘管在名稱中的其他任何地方使用數字都是完全合法的。

請考慮以下範例：

字串你的名字；

這告訴 BGT 我們想要一個字串類型的變量，稱為 your_name。當你回頭讀這段程式碼時，你就會知道your_name很可能儲存的是玩家的名字。再次，我們在末尾使用分號來表示語句的結束。

我們現在有一個空白變數。為了使這個變數可用，我們需要賦予它一個值。我們可以使用以下兩種方法之一來實現這一點。如果您已經預先確定了變數的內容，即可能顯示給使用者的訊息，則可以在宣告的同一行中指派該值，如下所示：

string winning_message="恭喜！您贏得了遊戲！";

這告訴 BGT：
•建立一個字串類型的變數。字串變數部分將更詳細地介紹字串。
•將其命名為winning_message。
•分配值「恭喜！您贏得了遊戲！」發送至winning_message。
•聲明結束

可以在當前程式碼區塊的任何時候為變數賦予新值。請注意，如果您沒有給變數分配值，而是只是像這樣聲明它：

類型 x；

類型可以是 BGT 支援的任何類型的原始變數（見下文）。物件的工作方式不同，但我們稍後會介紹這一點。如果您有這樣的聲明，但沒有為變數賦予初始值，那麼它將具有未定義的值。換句話說，它可能是任何東西，取決於變數的類型。它只包含變數建立時儲存在該記憶體位置的內容，因此，當變數尚未初始化時，您不應該使用它。當 BGT 腳本編譯器發現您執行此操作時，它會嘗試警告您，但它無法在每種情況下準確地發出警告。因此，您在建立變數時應始終為其賦予初始值，除非您能 100% 確定在使用該變數之前會為其賦予一個有意義的值。

現在我們已經討論瞭如何設定變量，我們可以了解不同的類型以及如何更改和比較它們。
5.3.積分變數和壞桃子
整數變數是包含基本整數（即整數）的變數。這些變數不允許小數點。
您可以對它們執行算術運算，如加、減、乘和除。如果您需要的話，BGT 還支援更複雜的數學函數。如果你好奇，這裡有一個在現實世界中如何使用積分變數的例子。

int蘋果=5；
int香蕉=2；
int橙子=8；
int 水果籃=蘋果+香蕉+柳橙；

這裡，您可以看到四個變數。一個名為蘋果，另一個名為香蕉，第三個名為橘子，最後一個名為水果籃。但是當我們給水果籃一個值時，我們實際上並沒有直接指定一個值，而是將三個水果類型變數的值加在一起以獲得籃子中的水果總數。讓我們開心一點，並看一下這個例子的稍微擴展的版本。

int蘋果=5；
int香蕉=2；
int橙子=8；
int 水果籃=蘋果+香蕉+柳橙+1；

我為什麼最後要加+1呢？因為其中也有一個桃子，但是它已經壞了，所以我沒有費心給它自己的變數。它還清楚地演示了在表達式中混合變數和文字數字是多麼容易，從技術上講，如果您願意的話，這允許您編寫非常強大的公式。

現在我們將稍微詳細說明並使用這些變數進行更多計算。假設我們想知道如果只放一半的橘子加上壞桃子，籃子裡會有多少水果。我們可以做以下事情：

int蘋果=5；
int香蕉=2；
int橙子=8；
int 水果籃=蘋果+香蕉+柳橙+1；
int robbed_basket=水果籃-(柳橙/2);

現在猜猜 robbed_basket 這個變數的值是什麼？沒錯，12個。籃子裡一共有16個水果，其中8個是橘子。然後我們拿走一半的橘子，最後剩下 12 個。

但再看一下 robbed_basket 變數取得其值的那一行。看到嗎，我們把部分計算放在左括號和右括號之間了？在這種情況下，這並不意味著我們正在呼叫某種函數，這裡它只是具有與常規算術相同的目的，即確保表達式按正確的順序計算。這樣，您可以計算高級數學表達式並確保結果始終符合您的預期。這是假設您一開始就正確地寫了表達式，這稍微超出了本教學的範圍。

我將給你最後一個例子來說明如何操縱數字變數。這個沒有什麼實際用途；它只是為了展示數字變數的靈活性，以及如何使用它們來執行常規數學中不允許的計算。請看以下內容...

int x=3；
x=x*3；
int y=8；
int z = x / y;
z+=x-1；
x*=z+4；

正如您所看到的，這只是一堆毫無意義的計算，除了試圖展示在使用 BGT 語言處理數字時的一些可能性之外，沒有任何其他用途。

然而，您會注意到的一個新事物是 += 和 *= 的使用。這意味著，在 *= 的情況下，數學表達式右邊的值應該會乘以變數的目前值。例如：

int x=5；
x*=3；

現在 x 的值為 15，因為 5*3 是 15。您可以以相同的方式使用所有數字運算符，即 +、-、* 和 /。
您可能希望使用另一個算術運算符，即模數運算符。它用百分號 (%) 表示，也可以與 = 符號一起使用。模數將兩個數字相除並得出餘數。例如：

int x=11；
x%=3；

在這種情況下，x 將相當於 2，因為 11 除以 3 等於 3，剩下 2。 2 是模數的結果。

您可能希望使用另外兩個節省時間的操作符。這些被稱為自動增量和自動減量運算子。這些是 ++ 和 -- （兩個加號或兩個減號）。這些適用於循環計數器等。
當我們寫下下面這一行：

x++；

我們告訴 BGT 將計數器增加 1。這會做兩件事。它可以節省您作為作者的時間，並加快程式的運行速度。如果您增加一個變數一次，您可能不會注意到變化。另一方面，如果您不斷改變計數器，您會注意到差異。儘管積分變數是一種類型，但您可以指定變數的潛在允許範圍。您通常感興趣的兩個是短整數，一個 16 位元（2 位元組）整數，範圍從 -32768 到 32767；以及長整數，一個 32 位元（4 位元組）長整數，範圍從 -2147483648 到 2147483647。
在其他情況下，儘管比較偶然，您可能希望使用無符號整數。這些是不允許負數的整數，因此最大限制加倍。
與整數相關的關鍵字完整清單如下：
• int8：8 位元（1 位元組）整數，範圍從 -128 至 127。
• int16：16 位元（2 位元組）整數，範圍從 -32768 至 32767。
• int32：32 位元（4 位元組）整數，範圍從 -2147483648 至 2147483647。
• uint8：8 位元（1 位元組）無符號整數，範圍從 0 到 255。
• uint16：16 位元（2 位元組）無符號整數，範圍從 0 至 65535。
• uint32：32 位元（4 位元組）無符號整數，範圍從 0 到 4294967295。
• int：int32 的別名。
• short：int16 的別名。
• long：int32 的別名。
• uint：uint32 的別名。
• ushort：uint16 的別名。
• ulong：uint32 的別名。
5.4.浮點變數
如果您使用過方程式，浮點變數與您熟悉的變數非常相似，它們包含一個數字。不多也不少。浮點變數可以帶小數，也可以不帶小數。
所有浮點變數都是有符號的，這意味著它們可以是正數或負數。
支援的類型有：
•浮點數：單精度 32 位元浮點數
• Double：雙精確度 64 位元浮點

如果您不知道變數中會出現什麼值，最好使用雙精度數，因為它們支援最廣泛的範圍，允許正數和負數，整數和實數，即帶有小數的數字。
還有一件事情要提一下，如果由於任何原因，數字變數的值試圖超出其限制，編譯器將發出警告，並且變數將重置為支援的最小值，因此追蹤變數發生的任何變化至關重要。
5.5.字串變數
字串變數與我們剛剛探討的數字變數有很大不同。但它們非常有用，我們很快就會明白為什麼。字串變數可以包含文本，也就是說，一串單一字元將形成一些有趣的東西；因此得名字串。字串可以保存播放器的名稱、硬碟上的路徑或檔案的內容，或任何您喜歡的內容。使用字串值時，必須始終用引號將它們括起來，這樣解釋器就不會混淆什麼是 BGT 程式碼以及什麼是字串的一部分。這聽起來很熟悉嗎？沒錯...我們在第一個範例中使用的警報函數適用於字串！

我認為掌握弦樂的最好方法就是觀察它們的實際作用。因此我們開始吧...

字串 my_name="John Doe"；
alert("我的名字是", my_name);

正如您在這裡看到的，警報函數的第一個參數仍然以與第一個範例中相同的方式給出，但不同之處在於第二個參數。我們沒有在引號之間指定要在訊息框中顯示的內容，而是給出了剛剛建立的字串變數的名稱。結果如何？那個“John Doe”被印在訊息框中，標題為“我的名字是”。是的，就這麼簡單！

現在，為什麼我們沒有在第二個參數中用引號將 my_name 括起來以進行提醒？因為那樣會直接印出“my_name”，而不是取得名為 my_name 的變數的值，而我們其實並不想要這樣。

讓我們嘗試用字串做一些更花哨的事情好嗎？

字串 my_name="John Doe"；
string message_string="我的名字是" + my_name + "，你永遠不要忘記它！";
alert("重要訊息", message_string);

什麼？這個「 + my_name + 」到底是什麼東西？嗯，「+ variable_name +」只是將變數的值插入字串中。因此訊息框中的列印輸出將是：

我的名字是 John Doe，你千萬別忘記！

您可能已經猜到了，兩個加號之間的變數不一定是另一個字串。讓我們用一個擴展的例子來說明這一點...

字串 my_name="John Doe"；
int年齡=23；
string message_string="我的名字是" + my_name + "，我的年齡是" + age + "歲，你永遠不要忘記這一點！";
alert("重要資訊", message_string)

列印件？正如你可能預料的那樣，它將會是：

我的名字是 John Doe，今年 23 歲，你們千萬別忘記這個名字！

您是否注意到我們也使用了整數？
任何資料類型的任何變數都可以放在字串變數中，只要變數的結果可以轉換為字串。
當然，您可以在實際呼叫警報函數時建構訊息字串；如果您不想的話，則不必為其建立單獨的變數。您可以執行以下操作...

字串 my_name="John Doe"；
int年齡=23；
alert("重要資訊", "我的名字是 " + my_name + "，我的年齡是 " + age + " 歲，請千萬別忘記！");

當然，列印輸出將是相同的，只是腳本短了一行。

讓我們對 John Doe 範例程式碼進行一些有趣的操作，並使其做一些更有趣的事情。看看下面的程式碼片段，看看你是否能弄清楚它的作用。

字串 my_name="John Doe"；
int年齡=隨機（5，50）；
string message_string="我的名字是" + my_name + "，我的年齡是" + age + "歲，你永遠不要忘記這一點！";
alert("重要訊息", message_string);

你猜對了。 BGT 引擎還提供了隨機函數，可產生您指定範圍內的隨機數。在這種情況下，我們要求一個介於 5 和 50 之間的數字，這意味著每次我們詢問 John Doe 時，他都會告訴我們不同的年齡。我第一次在我的電腦上運行這個程式時，它說 John Doe 25 歲，第二次運行它時它說他 36 歲，第三次運行它時它說他 18 歲... 簡直就是一個病態的騙子！

讓 John Doe 安靜地思考他的年齡，我們來看看如何將字串相加的另一個例子。

字串 string1="我是";
字串 string2="字串！";
字串字串3 = 字串1 + 字串2;
字串 string4=string1+“nice”+string2；

上面的例子顯示了將字串（變數和文字值）加在一起是多麼簡單。 string1 和 string2 是儲存在變數中的文字值，而 string3 是這兩個變數相加的結果，string4 是兩個變數再次相加，但中間有一個文字值。您可以按照任何您喜歡的方式組合變數和文字值，如果您經常在 BGT 中使用字串，您無疑會發現這在大量應用程式中非常有用。

正如我們在上一節中看到的，可以使用數字變數執行以下操作：

int x=10；
x+=15；

這當然會使 x 的最終值變為 25。現在，您會很高興地知道您可以對字串做同樣的事情。下面的程式碼完全合法：

字串 my_string="你好";
my_string+="我的朋友。";

當然，my_string 現在將包含文字「Hello there my friend.」。您可能已經猜到了，您不能以相同的方式對字串使用任何其他數字運算符（如 -，* 和 /），因為在這種情況下它們不會執行任何功能。
關於字串，還有另一件重要的事情要了解。為了能夠使用特殊字符，我們必須使用所謂的轉義字符。這是一個指定字符，它告訴編譯器下一個字符不應被視為文字字符，而應被視為應使用的字符的代碼。 BGT 中使用的轉義字元是反斜線。
以下是轉義字符後要使用的字符的列表以及它們真正代表的含義：
• \=\
• “=”
• n=新行
• r=返回
• t=標籤

以下是一些範例：

字串 string1="這是一個字串，使用標籤代替空格。";
字串 string2="這是一個\r\n多行\r\n字串。";
string string3="我目前的目錄是\"c:\\program files\\bgt\\my_game\\my_script.bgt\"";

注意多行字串如何使用 r 和 n 字元來建立新行。這是 Windows 使用的字元序列。
5.6.常量
常量與常規變數基本相同，但有一個主要差異。一旦為常數分配了值，它就不能在運行時更改。它的值在程式的整個生命週期內保持不變，因此稱為常數，而不是值可以隨時改變（即變化）的變數。
常量的目的主要是為了可讀性，並節省時間。
聲明常數的方式與聲明任何變數的方式相同，只需在資料類型前面加上關鍵字 const。
請看以下範例：

const 字串 snd_ext=".wav"；
聲波槍；
發出嘟嘟聲；
聲音氛圍；
聲音音樂；
槍.加載（“聲音/槍”+snd_ext）；
beep.load（「聲音/beep」+snd_ext）；
氛圍.流（“聲音/風” + snd_ext）；
音樂.串流（“聲音/音樂” + snd_ext）；

這裡的優點是，如果您的遊戲變得更大並且您決定轉換為 ogg 格式，您只需將 snd_ext 常數分配更改為「.ogg」。然後，如果所有聲音分配都使用常數，則它們都會變更為該值，而無需去更改每個單獨的聲音。
此外，BGT 引擎還附帶某些常數。在每個函數附帶的文檔中，都會提及何時需要使用某個常數。然後，常數會透過其名稱傳遞給函數，但實際發生的情況是，某個值（例如數字或字串）在後台傳遞。您只需使用常數名稱來使事情變得更容易，您也可以寫出數字本身並仍然獲得有效的程式碼。然而，例如以寫入模式開啟檔案時，名稱「file_write」比數字 2 更有意義。

還有一種特殊類型的常數，稱為枚舉。這是枚舉的縮寫，因此只能保存數字、整數值。這些對於將常數分組在一起很有用，從而實現更好的程式碼管理。以下是一個例子：

列舉移動
{
左=-1，
右=1
}

列舉武器
{
拳頭，
俱樂部，
彈弓，
槍，
炸彈
}

我們在這裡所做的是創建兩組枚舉，一組稱為運動，一組稱為武器。在每個枚舉中，我們都為解釋器提供了一個以逗號分隔的常數列表，並且在我們的運動範例中，也提供了它們對應的值。例如，我們將名稱左指定為 -1，將名稱右指定為 1。但是，對於武器，我們沒有賦予任何價值。這是因為解釋者可以自動假設我們的價值觀。如果沒有給出值，則第一個常數將被分配值 0，之後的每個常數將增加 1。這裡很有用，因為我們可以專注於決定我們想要什麼武器。它還有一個額外的好處，如果我們將來想添加額外的武器，則無需進行數位變換。

請注意，枚舉必須全域定義；它們不能在函數內部定義。
5.7.概括
讓我們快速瀏覽一下本章所涵蓋的內容以及一些額外的提示。

•變數就像籃子，可以容納不同類型資料。
•常數是運行時不能改變其值的變數。
•積分變數可以保存正數或負數，取決於您將它們聲明為有符號還是無符號，但不能有小數。
•浮點變數可以保存正數或負數，帶或不帶小數。
•您可以對數字變數執行算術運算，例如 3+5=8，既可以使用其名稱引用的變量，也可以使用文字值。
•如果您不知道將儲存在數字變數中的值，最好使用雙精確度型。
•始終追蹤您的變量，否則如果超出限制，您可能會得到意想不到的結果。
•字串是構成有用內容的字元清單。
•您可以將名稱所引用的變數與引號中的文字值組合起來，從而組裝成新的字串。
•將兩個數值變數相加，其值皆為 1，結果為 2。
•將兩個字串變數相加，它們的值都是「1」（請注意引號），結果為 11。
•變數是遊戲開發和程式設計的核心基礎之一。
• BGT 中的變數和常數區分大小寫。因此，您必須按照聲明它們的方式或它們在文件中的出現方式來引用它們。
6. 功能
我們已經看到了幾個函數呼叫的例子。在我們的第一個例子中，當我們想要在螢幕上列印一條訊息時，我們呼叫了 alert 函數。現在，我認為是時候更深入地了解這些功能了，因為它們幾乎用於市場上的每個遊戲。函數是一段可以用名稱引用的程式碼。例如，你可以有一個名為 jump 的函數，讓玩家跳躍。每當您想讓玩家跳躍時，您就會呼叫此函數，其中的程式碼就會執行。函數非常有幫助，因為它們不僅允許您以更合乎邏輯的方式建立程序，還允許您多次重複使用相同的程式碼段，而不必在每次想要執行時實際編寫或貼上它。

到目前為止我們呼叫的所有函數都是 BGT 引擎本身的一部分，但您也可以建立自己的函數來完成任務。事實上，在引擎接受您的程式碼之前，您必須建立一個函數。您的程式碼的主要部分放在這裡。這是呼叫附加函數的地方，可以是來自 BGT 引擎的函數，也可以是來自包含的其他腳本的函數，或是目前腳本中包含的您自己的函數。因為這個函數儲存了所有的主要程式碼，所以這個函數被稱為main。當我們討論將訊息列印到螢幕上時，我們簡要地了解了這個功能。
像往常一樣，在您再次被一大堆新的理論胡言亂語所淹沒之前，我們將先看看它在實踐中是如何實現的。

空主（）
{
alert("測試", "我們現在正在使用主要函數。");
}

您不需要自行呼叫此函數，因為引擎使用此函數作為起點。
“void”這個字表示函數沒有回傳類型。我們將在本章後面討論返回。然後您將得到函數的名稱，最後得到括號內的參數列表。 main 函數不接受任何參數，因此參數清單為空。在函數內部，我們僅顯示另一個警告框，然後使用右括號字元告訴 BGT 解釋器我們的函數已完成。就這麼簡單！幾乎...

為了有用，很多函數都需要採用參數。例如，BGT 引擎中的隨機函數需要取允許產生的最小數字和最大數字。您自己的函數也可以接受參數，並且在您建立函數時它們被放在左括號和右括號之間。讓我們看一個將兩個數字相加，然後將該值傳回給呼叫者的函數。

空主（）
{
int x =添加數字（3，5）；
alert("哇", "3 + 5 是... " + x + "！");
}
int 加數字（int 第一個，int 第二個）
{
int 結果=第一+第二；
返回結果；
}

這個例子引入了幾個新事物。首先，它展示瞭如何在函數中接收參數。您可以透過在建立函數時指定變數名稱來執行此操作，在本例中，我為兩個數字選擇了第一個和第二個。然後，無論何時呼叫此函數，傳遞給它的兩個參數都會被放置在兩個對應的變數中。這意味著第一個名為的變數接收第一個參數，而第二個名為的變數接收第二個參數。當然，你可以給你的函數參數取任何你喜歡的名字，只要它們是合法的變數名稱。我們也看到了函數如何使用 return 語句將值傳回給呼叫者。返回語句將完全中斷函數並返回空格後給出的值或變量，然後呼叫者可以透過將函數的返回值分配給新變量來捕獲它，就像我們在示例中最開始看到的那樣。在我們的例子中，我們使用變數 x 從函數中取得值，然後在普通警告框中將其列印出來。
您可能還注意到，我們沒有以單字“void”開頭函數聲明，而是以單字“int”開頭。 void 這個字是一個關鍵字，只在函數開始時使用，以告訴引擎函數不會傳回任何數據，但 return 關鍵字可以單獨使用來跳出函數，即過早終止其執行。因為我們傳回加法和的結果，所以我們告訴引擎我們希望回傳一個 int。
函數宣告以傳回所需的資料型別開始，如變數所述，並附有附加關鍵字 void。然後我們給我們的函數命名。緊接著的是我們的參數，用逗號分隔並用括號括起來。然後打開一個括號，因為函數是一個程式碼區塊。
您不僅可以從函數返回變量，文字值也完全沒問題。當然，我們用另一個例子來證明這一點。

空主（）
{
字串 x=string_magic()；
alert("結果", "給定的字串是：" + x + "。");
}
字串 string_magic()
{
返回“哇”；
}

您可能已經注意到，這是一個完全沒有意義的函數。它不需要任何參數，只傳回一個文字字串。然而，在開始編寫適當的遊戲之前，最重要的是您要完全理解函數的概念。因此，我將再舉一個例子。讓我們回到我們非常有趣的數字加法函數，但略作改動。

空主（）
{
alert("哇", "3 + 5 是... " + add_numbers(3, 5) + "！");
}
int 加數字（int 第一個，int 第二個）
{
返回第一+第二；
}

輸出與以前完全相同，我們的腳本只是短了兩行。我們所做的唯一一件事就是刪除兩個不太必要但可能使事情更容易理解的變數。我們不是將函數的返回值分配給 x 變量，而是在調用 alert 函數時直接列印它。正如您所看到的，在一個函數呼叫內進行另一個函數呼叫是沒有問題的。您可以根據需要嵌套任意數量的函數調用，直到達到 10000 次調用，此時 BGT 解釋器將不會對您感到滿意。我們將再次看一下 string_magic 函數範例，但又稍有不同。我們不會在所有範例中都包含主函數，只要您知道任何不屬於其他函數的程式碼都會進入 void main。

//主要函數
alert("結果", string_magic() + string_magic() + string_magic());
字串 string_magic()
{
返回“哇”；
}

是的，我知道，又毫無意義了。該腳本的輸出是“wowwowwow”。它只是展示了另一種方式，你可以在呼叫另一個函數的過程中呼叫一個函數，依此類推。在另一個函數體內呼叫一個函數也是可以的。函數甚至可以呼叫自身，但要小心，因為如果您錯誤地編寫了腳本，它可能會導致所謂的堆疊溢出，即在彼此之上進行過多的巢狀函數呼叫。如同前面提到的，其限制是 10000 次嵌套呼叫。

在第一個函數出現之前在腳本頂部聲明的所有變數都被視為全域變數。可以從腳本中的任何函數內部存取它們。但是，在函數內部宣告的新變數僅限於該函數的局部。這意味著如果您有一個存儲某些資訊的局部變量，然後函數退出，那麼該變量將丟失，除非它被用作函數的返回值。因此，您希望在多個函數中存取的任何資訊都應儲存在一個或多個公共變數中，除非您希望將它們作為所有函數之間的參數傳遞，這通常不建議。
您現在應該知道，任何大量的理論通常都會伴隨著一個例子，所以讓我們開始吧。

// 讓我們建立一些全域變數。

字串 my_name="John Doe"；
int手指數量=5；
空主（）
{
我的第一個函數（）；
}
無效 my_first_function()
{
//我們現在建立的變數是局部的。
int 手數=2；
int 寵物數量=13；

//現在我們從這個函數內部修改全域變數my_name。
我的名字=「羅德尼」；
}

我承認這是一個非常愚蠢的例子，但它仍然達到了目的。如您所見，在腳本頂部聲明的所有變數都可以從函數內部訪問，而在其內部聲明的變數只能從函數本身存取。因此，在另一個函數中修改 number_of_pets 的值是不合法的，除非你在那裡聲明了一個同名的函數。

一個函數只能傳回一個值。執行以下操作是毫無意義的：

int 我的函數（）
{
返回3；
返回5；
}

如前所述，return 關鍵字將傳回其值（如果有），銷毀所有函數變數並忽略函數中的任何進一步程式碼。因此，函數只會傳回 3。但是，如果您需要傳回多個值，有一種方法可以解決這個問題。
參數通常透過值傳遞給函數。這意味著，如果您將變數作為參數傳遞給函數，則解釋器將讀取該變數的值並將其傳遞給函數。這是一個例子。

//主要函數
int x=5；
我的函數（x）；

解釋器將建立一個名為 x 的 int 類型變數並為其指派數字 5。然後它會檢查 x 的值，該值最終為 5，並呼叫 my_function，並將 5 作為參數傳遞。

但是，參數也可以透過引用傳遞給函數。解釋器不會讀取我們的變數並將其傳遞給函數，而是傳遞變數本身。這意味著如果函數隨後修改了變量，則該修改也將應用於原始變量。透過這種方式，我們可以透過引用修改變量，輕鬆地從函數傳回多個值。另一個例子：

//全域變數

int x=1；
int y=2；

//主要函數
我的函數（x，y）；

void my_function(int &out 第一，int &out 第二)
{
第一=5+5；
第二=5*5；
}

您可能想知道為什麼我們使用 void？這是因為，如前所述，我們透過參數傳回值，而不是函數本身。如果您現在檢查 x 和 y 的值，它們不再是 1 和 2，而是 10 和 25。
您可能注意到的另一件事是，在我們的參數的資料類型之後，我們輸入了 &out（& 符號後緊接著單字 out）。 “與”號告訴解釋器我們即將指定如何傳遞參數。如果沒有找到 &（與）符號，解釋器將假定 &in 標誌，這意味著我們透過值傳遞參數。
當我們透過引用傳遞參數時，您需要傳遞一個變數而不是常數。解釋器不會阻止您傳遞常數，但它會發出警告，指出所做的任何更改都會遺失。

必須注意的是，任何函數宣告都不能重複，因為這沒有任何用處。但是，一個函數可以被聲明多次，只要每次聲明的參數不同即可。這被稱為重載函數，並透過更多範例進行展示。

//主要函數
字串文字=my_function(“Daniel”,5);

int my_function（int 第一，int 第二）
{
返回第一+第二；
}
字串 my_function(字串 your_name，int 數字)
{
return "你好，"+your_name+"。樓下有 "+number+" 個人在等你。";
}

因為我們使用字串作為第一個參數來呼叫 my_function，所以解釋器會呼叫我們的第二個 my_function 宣告。

您也可以為函數參數提供預設值。這僅僅意味著您不必傳遞函數所需的全套參數，而只需傳遞幾個參數，然後函數就會使用其他可選參數的預設值進行呼叫。以下是一個範例：

空主（）
{
印刷（“你好！”，10，20，30）；
印刷（“再次問好！”，10）；
}

void print(string title, int value1=0, int value2=0, int value3=0)
{
alert(title, "值 1 為 " + value1 + "，值 2 為 " + value2 + "，值 3 為 " + value3 + "。");
}

這裡我們宣告一個名為 print 的函數，它接受四個參數。第一個是用於我們顯示的訊息標題的字串，這個參數是強制性的。我們看到這種情況是因為在參數名稱後面沒有給出預設值。但是，對於其他三個值，名稱後面有一個 = 符號，然後是一個預設值（如果呼叫者未指定此參數）。我們在範例中看到，第一次呼叫 print 指定了所有四個參數，而第二次呼叫僅指定了其中兩個參數。因此，在顯示的第二個訊息中，最後兩個整數被設定為 0。

在給定函數中，可以有任意數量的可選參數。但請注意，可選參數之後不能有強制參數。所有強制參數必須位於第一個可選參數之前，否則，如果在剩下強制參數時傳遞的參數太少，編譯器將陷入困境。
。
在繼續本教程之前，充分理解這些概念非常重要。此時，我建議您去研究函數參考中的某些內容，看看它們是否完全有意義。如果沒有的話，你可能需要再讀一遍本章，直到一切都清楚為止。
7.條件語句
到目前為止，我們僅從已知值的角度對變數進行了探索。儘管在程式中你經常需要能夠在不知道變數確切值的情況下執行一些操作。舉例來說，當玩家的健康狀況低於 20% 時，您可能會想讓玩家的心臟開始快速跳動。為此，我們必須使用所謂的條件語句。
7.1. If 語句
If 語句的工作原理與日常語言中的工作原理完全相同。你可能會對你媽媽說：「如果我身體不舒服，請告訴我爸爸。」雖然電腦程式無法完成如此複雜的任務，但它們可以基於相同的邏輯完成電腦任務。
如果您有一個簡單的條件或一組需要根據情況採取行動的條件，那麼 If 語句很有用。

BGT 中的 if 語句可能如下所示：

如果（健康 < 21）
{
// 播放心跳聲音。
}

這裡發生的情況是，當解釋器到達 if 語句時，它將檢查它是否為真，也就是說健康變數的值是否低於 21。如果是，它將執行下面 if 語句和右括號之間的程式碼。您也可以執行以下操作：

如果（健康 < 21）
{
// 播放心跳聲音。
}
別的
{
// 如果健康狀況不低於 21，則執行其他操作。
}

在這種情況下，可能會發生兩件事。如果健康值低於21，則執行第一個程式碼段。然而，如果條件不真，則會執行另一部分程式碼，即 else 和 } 語句之間的程式碼。簡而言之，當條件為真時，這可以使一件事發生，當條件不真時，可以使另一件事發生。
另一個有趣的點：如果當 if 條件為真時只有一件事要做，則不必使用括號。以下是一個例子：

//帶括號的單一動作條件

如果（健康<21）
{
//播放心跳聲音
}

//單動作條件，無括號

如果（健康<21）
//播放心跳聲音

//帶括號的多個動作條件

如果（健康<21）
{
//播放心跳聲音
alert("警報！", "您即將死去..."); //顯示訊息
}

重要的是要記住多個動作必須包含括號。事實上，為了更容易管理程式碼，建議在所有情況下都使用括號，這樣就不會發生錯誤。如果您將來想在該 if 語句中添加任何進一步的操作，它將變得更容易。

在上一節中，我告訴過您，在函數內部聲明的任何變數都僅限於該函數的局部變數。對於 if 語句來說也是如此。在 if 語句中宣告的任何變數都是該語句的本地變量，不能在外部使用。在 if 語句中宣告變數並不是每天都會發生的情況，但在某些時候可能會很有用。這同樣適用於任何程式碼區塊，包括循環。
當然，您還可以執行許多其他檢查。以下是完整列表：

• if(x == y) - 若 x 等於 y，則為真。您可以對任何類型的變數執行此檢查。
• if (x != y) - 若 x 不等於 y，則為真。您可以對任何類型的變數執行此檢查。
• if(x < y) - 若 x 小於 y，則為真。您只能對數字變數執行此檢查。
• if(x <= y) - 若 x 小於或等於 y，則為真。您只能對數字變數執行此檢查。
• if(x > y) - 若 x 高於 y，則為真。您只能對數字變數執行此檢查。
• if(x >= y) - 若 x 大於或等於 y，則為真。您只能對數字變數執行此檢查。
• if(!x) - 若 x 為假，則為真。這只能用於布林變數。

您可能想知道為什麼我們在檢查 x 是否等於 y 時使用兩個等號？這是因為使用一個等號我們會將 y 的值分配給 x，而這並不是我們想要的。故說：

如果（x=y）

因此既不合邏輯，也不正確。

讓我們用一個更實際的例子來說明這一切。我們將再次回到 John Doe 和他的問題來確定他自己的年齡，但這次我們將根據生成的隨機數字打印他是否是未成年人。回到前面的例子，再看一下下面的程式碼片段。

字串 my_name="John Doe"；
int年齡=隨機（5，50）；
string message_string="我的名字是" + my_name + "，我的年齡是" + age + "歲，你永遠不要忘記這一點！";
alert("重要訊息", message_string);

熟悉的？很好，因為這不會持續很長時間...現在試著猜測當我們執行以下操作時會發生什麼。

字串 my_name="John Doe"；
int年齡=隨機（5，50）；
string message_string="我的名字是 " + my_name + "，我的年齡是 " + age + " 歲，這表示我是 ";
如果（年齡<18）
{
message_string+="未成年人！";
}
別的
{
message_string+="成年！";
}
alert("重要訊息", message_string);

上面的程式碼確實非常簡單，但我們將逐步檢查它以確保它 100％清晰。首先，我們建立名為 my_name 的變量，在這種情況下完全沒有必要，但它是原始範例的一部分，所以我們允許它保留。然後，我們產生一個 5 到 50 之間的隨機數並將其指派給名為 age 的變數。此後，我們製作包含最終訊息的字串。您會注意到該句子並不完整，但這並不奇怪，因為我們即將在 if 語句中完成它。如果年齡低於 18 歲，我們會在字串中添加一些文字，表明 John Doe 是未成年人。如果不是，則執行另一段程式碼，使字串聲明 John 已成年。最後，當然，我們會將其印在我們舊的訊息框中。嘗試運行此程式碼，看看您是否理解列印結果背後的邏輯。

您可能已經意識到，一個 if 語句內部完全可以包含另一個 if 語句。這使得只有滿足幾個條件時才能使某件事發生。您也可以在 if 語句中執行多重比較。我們將在本章稍後探討這個問題。

現在我們已經了解如何使用 if 語句，接下來我來向您介紹一種新類型的變數…布林值。布林值只能保存兩個值，真或假。這些值通常用於 if 語句中，例如：

bool has_weapon = true;
如果（has_weapon==true）
{
// 做一些事情。
}

單字 true 和 false 不是任何預先定義變數或常數的名稱，而是語言中的實際關鍵字。因此，你不能說：

假=5；

因為 false 已經被用來當作關鍵字。另一方面，當使用布林變數時，您可以執行以下操作：

bool has_weapon = true;
如果（有武器）
{
// 做一些事情。
}

乍看之下這可能很奇怪，因為我們實際上並沒有指定任何應該滿足的條件，但由於 has_weapon 是一個布林變數並且具有值 true，所以 if 語句內的程式碼將執行，因為整個表達式將為真。類似地，如果 has_weapon 被設定為 false，則 if 語句內的程式碼將不會被執行，因為整個表達式將為 false。

為了示範布林變數以及其他變數類型的不同用途，我們將查看名為 key_down 和 key_pressed 的函數的簡單用法範例。這些功能用於檢查某些鍵的狀態。看一下下面的一段程式碼：

如果（按鍵按下（KEY_F4））
{
如果（key_down（KEY_LMENU））
{
alert("好吧！"，“如果你想無聊地退出這個程序，那就盡你所能吧。再見。”);
出口（）;
}
別的
{
alert("嗯...", "您是否要關閉該程式？");
}
}

在這裡，我們檢查兩個不同的條件是否真實，並根據發現的結果採取行動。首先我們檢查是否按下了 f4 鍵。如果確實如此，我們檢查 alt 鍵是否被按下。如果這也是事實，我們假設用戶正在關閉該程式。如果不是這樣，我們會顯示一條嘲諷訊息，因為他們忘記按住 alt 鍵。

最後，我們有兩個右括號，以便所有 if 語句都正確關閉。您可以根據需要以任何您喜歡的方式嵌套任意數量的 if 和 else 語句，只要在正確的位置使用適當的括號平衡它們即可。

您可能會問，有沒有辦法測試多個條件是否為真？是的。邏輯運算子可讓您在 if 語句中檢查多個條件。
請看以下例子。我們將再次檢查 Alt+F4，但這次不會顯示任何訊息。我們將直接退出。

如果（（key_down（KEY_LMENU））&&（key_pressed（KEY_F4）））
{
出口（）;
}

您可能會注意到這裡有兩件主要的事情。首先，我們的 if 語句包含三組括號。一組包含整個 if 語句，另外兩組包含每個條件。這純粹是為了易讀性的目的。我們也可以很容易地說

如果（key_down（KEY_LMENU）&&key_pressed（KEY_F4））

並且得到了相同的結果。
您可能注意到的另一件事是 &&（雙 & 符號）。這代表單字和。換句話說，它告訴引擎，如果按住 Alt 鍵並按下 F4 鍵，則退出。
下面是另一個範例：

如果（（key_down（KEY_LMENU））||（key_pressed（KEY_F4）））
{
出口（）;
}

這在本例中沒有實際用處。如果按下 alt 鍵或 f4 鍵，它會告訴引擎退出。但是，如果您將兩個鍵指派給一個功能，這可能會很有用，例如向下和向右箭頭會在選單中向下移動一個選項。
最後一個運算符，我們之前簡單提到過，是否定運算符。這寫為！ (驚嘆號).這將反轉條件的結果。例如：

如果（！key_down（KEY_LMENU））

它不會檢查 key_down(KEY_LMENU)==true，而是檢查它是否為 false。
請注意，該運算子不用於比較值。這只是逆轉了給定條件的影響。使用上面的例子：

如果（！key_down（KEY_LMENU））

請注意，只有一個條件和一項檢查。這實際上是反轉了值的狀態，而不是比較兩個值的錯誤程度。例如，如果使用者按下 Alt 鍵，則函數傳回 true。因此否定運算子將其變為假。要檢查給定值是否等於（真）或不同於（假）另一個值，可以這樣寫：

如果（我的變數1！=我的變數2）

無論從邏輯上還是語法上來說，這都更有意義。因為感嘆號代表否定，換句話說就是不，等於是不言自明的，翻譯成英文就是：如果 my_var1 不等於 my_var2。它不會像否定運算子那樣直接反轉結果，而是在條件為假（即兩個變數不相等）時傳回 true。
7.2.開關和外殼
Switch...case 語句與 if 語句非常相似，但它們用來佈置大量或更複雜的檢查。
以下是一個例子：

健康；
雙倍能量；

開關（健康）
{
案例 100：
能量=100；
休息;
案例90：
能量=89；
休息;
案例80：
能量=78；
休息;
案例70：
能量=67；
休息;
案例60：
能量=56；
休息;
//此處新增其他值
}

關鍵字 switch 用來啟動 case 語句，後面跟著括號內要檢查的變數。每個條件都由關鍵字 case 引入。 Switch 語句的主體用括號括起來。
在 switch 語句中每個 case 的結尾，通常會使用語句 break 來完成該 case。
如果省略 case 末端的 break 語句，執行將繼續到下一個 case。儘管這有時很有用，但還是需要注意。
可以使用的另一個關鍵字是 default。這將針對 case 語句未滿足的任何其他條件執行任務。以上面的例子為例，我們可以透過執行以下操作使玩家的能量減少更加真實：

開關（健康）
{
案例 100：
能量=100；
休息;
案例90：
能量=89；
休息;
案例80：
能量=78；
休息;
//此處新增其他值
預設:
能量-=0.5；
}

這意味著現在每次執行此 case 語句時，當我們的健康狀況達到可被 10 整除的數字時，它都會將我們的能量設定為預定值，而在所有其他情況下僅減去 0.5。
請注意，即使是預設情況也會出現中斷。您可以按照您希望的任何順序排列這些案例。

切換條件內的表達式可以是任何產生值的表達式，只要結果是數字即可。例如，您可以：

開關（數字 % 100）

但是，switch 中的每個 case 都必須是常數：不能使用以下表達式：

情況 n < 5：

或者

案件編號：

如果這樣做，編譯器將會發出錯誤訊號。

此外，不能有多個具有相同值的案例，也不能有多個預設案例。

與往常一樣，如果需要，可以巢狀 switch...case 語句。
8. 循環
在對 if 語句進行一些實驗之後，您應該了解循環的概念，因為它們非常相似。循環是一段不斷執行直到某個條件成立的程式碼。它們在遊戲中被廣泛使用，例如您需要一個循環來連續檢查某些擊鍵以及是否發生了某些特定事件等等。
執行迴圈有三種方法，我們將在本章中討論每一種方法。
8.1. While 循環
看一下下面的程式碼範例。

while(key_pressed(KEY_ESCAPE)==false)
{
等待（5）；
}

令人困惑？沒問題，我們會一點一點解決的。第一行確實是最複雜的。首先有單字“while”，這意味著循環應該從這裡開始。此後，您就獲得了循環運行所需滿足的條件。在這種情況下，我們使用名為 key_pressed 的函數來檢查是否已按下 Esc 鍵，並在該函數報告該鍵實際上未被按下時使循環運行。簡而言之，循環將持續運行，直到使用者按下“Esc”鍵。然後，while 語句和右括號之間的程式碼會一遍又一遍地執行，並且每次執行之後都會再次測試條件以查看它是否為真。如果是，則開始執行右括號後的程式碼。在我們的循環中，我們只讓程式等待 5 毫秒，這基本上會在每次檢查條件之間暫停執行很短的時間。如果我們不包含此行，處理器的 CPU 將開始以 100% 運行，這並不是我們真正想要的。因此，在任何較長時間運行的循環中，您始終應該有一個短暫的暫停，並且 5 毫秒通常是速度和 CPU 使用率之間的一個很好的折衷。

但假設您想要執行特定任務 x 次，循環是正確的選擇嗎？當然。這裡有一個簡單的例子，展示如何讓某件事發生 1000 次。

int x=0；
當（x<1000）
{
// 在此輸入您的程式碼。
x++；
}

我們首先建立一個名為 x 的變數並賦予其 0 的值。然後我們開始循環，並告訴它在 x 低於 1000 時執行。此後我們插入想要執行的任何程式碼，最後將 x 增加 1。這樣，x 將在每個循環週期中增加 1，以便最終獲得值 1000。當它這樣做時，對條件的下一次檢查顯然會為假，因為 x 不再低於 1000。實際上，我們已經執行了 1000 次程式碼，x 從 0 變成 999。當然，我們可以給 x 一個初始值 1，並讓循環在它低於 1001 時運行，或者在它低於或等於 1000 時運行（有關詳細信息，請參閱 if 語句章節），但我們選擇從 0 開始有一個特殊原因，我們將在下一章中進一步解釋。
這是另一個例子。我們將在訊息框中顯示數字 0 到 10。
int x=0；
字串數字="";
當（x<=10）
{
數字+=x；
如果（x<10）
{
數字+=" ";
}
x++；
}
alert("列印數字 0 到 10", numbers);

當 x 小於或等於 10 時，迴圈執行，換句話說，直到 x 等於 11。

我們可以稍微縮短此程式碼，如下所示：

int x=0；
字串數字="";
當（x<=10）
{
數字+=x++；
如果（x<=10）
{
數字+=“”;
}
}
alert("列印數字 0 到 10", numbers);

這裡，我們在與檢查過的位置完全相同的位置更改了 x。 X 的值會在被檢查之後改變（注意 x 上的 ++ 運算子）。
我們也稍微改變了 if 條件，以檢查 x 是否小於或等於 10。這是因為否則空格將從 9 開始向上停止，因為在檢查 if 條件之前 x 已經增加了。
8.2.執行 while 迴圈
我們已經了解了 while 循環，現在是時候向您展示一些非常相似的東西了。讓我們先等待使用者再次按下 Esc 鍵：

做
{
等待（5）；
}
當（！key_pressed（KEY_ESCAPE））時；

這裡有兩點值得關注。首先，do 這一行告訴 BGT 迴圈從哪裡開始。程式碼區塊再次括在括號中，是條件為真時要重複的程式碼部分。最後，右括號後面還有一行附加內容，告訴解釋器需要滿足哪些條件才能讓迴圈繼續。
您可能注意到，在這個 while 語句中，條件之前有一個感嘆號。這告訴它只要條件為假就重複循環。簡而言之，以下兩行意思完全相同：

while(key_pressed(KEY_ESCAPE)==false)

while(!key_pressed(KEY_ESCAPE))

重要的是要記住，在 do while 迴圈的 while 語句之後有一個分號，因為這相當於給出一條指令。當滿足該條件時，執行上述操作。
while 迴圈和 do while 迴圈之間有一個顯著的差異。 while 迴圈首先檢查條件，這表示如果條件不滿足，則可以完全跳過程式碼。但是，使用 do while 循環，直到循環結束才指定條件，這表示程式碼總是執行至少一次。
8.3. For 循環
for 迴圈是迴圈中最強大的，主要用於計數。本質上，它將循環控制的各個部分組合成一個語句。
考慮上述範例中的 while 迴圈。在我們開始循環之前，變數被初始化為起始值（在我們的例子中，它是 0）。然後，我們在 while 迴圈的頂部有了 while 條件。最後，就在 while 的結束括號之前，我們重新初始化 x 以準備下一次運行。
在 for 迴圈中，這三個階段被打包成一行：

對於（int x = 0; x <= 10; x ++）
{
//循環程式碼放在這裡。
}

for 語句有三個部分：
首先，我們將變數初始化為起始值。這裡，我們選擇 0。雖然我們在循環內部創建了變量，但使用現有變量是完全合法的，就像您在腳本的任何地方修改其值一樣。
其次，我們告訴它在什麼條件下我們的循環將繼續運作。在我們的例子中，如果 x 小於 10。
最後，我們告訴它如何重新分配變數以準備下一個循環。儘管我們像在大多數其他語言中一樣將 x 增加了 1，但重新分配表達式可以是任何你喜歡的內容。看一下下面的程式碼。

對於（x=1；x<1000；x*=2）
{
//程式碼放在這裡
}

循環的每個週期都會將 x 的目前值乘以 2。
8.4.中斷並繼續
我們需要討論最後兩種可能的情況。有時您希望很多事情連續發生，但您可能不知道何時需要停止循環，或者您可能因為幾個不同的原因而想要停止循環，而這些原因不能全部用作條件。在這種情況下，您可以執行以下操作：

while(true)
{
如果（某事==真）
{
休息;
}
如果（其他情況 == true）
{
休息;
}
如果（另一個變數 == true）
{
繼續;
}
// 這裡有更多程式碼...
等待（5）；
}

這裡有三件新事物。第一件事是在第一行，我們並沒有明確的循環條件。我們所說的只是“while(true)”，乍一看這確實沒有什麼意義。然而，這並不奇怪，因為 true 當然永遠是 true，這意味著循環永遠不會結束，因為條件永遠不會為假。簡而言之，我們創建了一個無限循環。當然，我們不希望這個循環永遠持續下去；我們確實想在某個時候停止它，但我們在循環內部進行此操作。上述三個 if 語句檢查實際上不存在的條件，因此如果您執行此腳本，它會給出錯誤。我想表明的是，您可以根據自己的判斷，隨時使用前兩個 if 中的 break 語句以任何理由退出迴圈。在查看 switch...case 語句時，我們簡要地看到了 break 語句的實際作用。 break 語句基本上強制迴圈終止並開始執行其後面的程式碼。如前所述，當您由於多種原因想要退出循環，或者當您不知道何時應該停止時，這非常有用。
必須注意的是，如果循環嵌套，break 語句將只能跳出宣告它的循環。

您在第三個 if 語句中看到的另一個關鍵字是 continue。再次強調，這個關鍵字可以在循環內的任何地方使用。 continue 關鍵字告訴解釋器跳過該循環的剩餘部分並轉到下一個循環。這很有用，例如，當您從文件中讀取某些內容（例如需要跳過的空白行）時。
9.數組和字典
到目前為止，我們已經了解了變數的基本用途。我們已經了解如何操作它們，以及如何在一般的程式設計環境中使用它們。然而，您遲早無疑會發現一個大問題，那就是即使在中型遊戲中，您也必須追蹤大量不同的變數。請想像一下，您有一個帶有 50 個方格的遊戲板。使用者需要擲一對骰子，他們的位置會根據擲出的數值增加，然後您要檢查玩家所落腳的方格上會發生什麼。為了這個簡單的例子，我們假設表示正方形的變數可以有 3 個值； 1、2 和 3。當然，這些值可以表示您想要的任何意思。 1 可能意味著用戶後退三格，2 可能意味著他們可以留在原來的地方，而 3 可能意味著他們可以向前移動三格。現在讓我們嘗試快速建立這個遊戲板的一部分。我們將第一個方塊設為數字 0，而不是數字 1，原因很快就會清楚。

int board0=2；
int 板1=2；
int 板2=2；
int 板3=2；
int板4=1；
int板5=2；
int 板6=2；
int 板7=2；
int板8=2；
int板9=3；
int 板10=2；
int board11=2；
int board12=2；
int 板13=2；
int board14=1；
…

我們就此打住，因為如果您列印出本教學課程，您無疑會對浪費的紙張數量感到憤怒。看完此範例後，您會同意程式碼非常重複且乏味嗎？假設這是一款橫向捲軸遊戲，不同的數字代表地面、火和水等，如果關卡很大，你可能會有 300 個方格。想像寫出 300 行如上所示的句子的樂趣......但這不切實際。不僅要花很長時間來寫下網格或棋盤的實際內容，而且還必須有 300 個 if 語句來確定在玩家移動到新方格後應該檢查 300 個變數中的哪一個。你能說出噩夢嗎？我是這麼想的。幸運的是，有一個足夠簡單的解決方案......數組。

簡單來說，數組就是變數的列表。數組本身俱有與其他變數一樣的名稱，但它包含可以透過索引引用的不同值。數組可以根據需要變小或變大。像往常一樣，我們來看一個例子來滿足我們的胃口。

int[]板(50);
對於（int x = 0; x <50; x ++）
{
板[x]=2；
}

等一下，等一下……這裡有很多新東西。讓我們像往常一樣逐行檢查程式碼。第一行告訴引擎要建立一個新的數組，也就是一個 int 類型的變數列表。 int 後面的方括號告訴引擎該變數是陣列。然後將數組指派給名為 board 的變量，以便我們稍後可以引用數組內的條目。括號內的數字告訴引擎為該數組提供 50 個條目。在下一行中，我們使用 for 循環建立一個名為 x 的新變量，然後啟動一個循環，當 x 低於 50 時運行。在此之後的一行中，發生了一些非常有趣的事情。這裡我們為 board 陣列中的一個條目分配一個值 2。但是，在這種情況下，此條目未指定文字值。這當然是可能的，但是使用循環更有意義，因為這樣我們就不需要明確地指定每個單獨的條目，當然，這樣我們的情況就會和上一個例子中的各個變數一樣糟糕。您將發現數組的索引（即要存取的清單中的條目）是在左括號和右括號之間指定的，而不是您可能期望的左括號和右括號。在最後一行，我們只是告訴引擎我們的循環到此結束。我們所做的是遍歷整個 50 個項目的列表，並自動為每個項目分配一個值 2。此後，我們只需要手動為列表中那些應該不是 2 的條目分配值。比之前的噩夢範例簡單得多，對吧？我是這麼想的。

現在你可能也了解我們在原始範例中將棋盤上的第一個方格指定為 0 而不是 1 的原因。這只是因為數組中的第一個索引始終是 0，而不是 1。所以如果你有一個包含 100 個條目的數組，並且你希望循環遍歷它來執行某些任務，你將從 0 開始一直到 99。如果你嘗試存取超出範圍的數組中的條目，則會出現運行時錯誤。這意味著解釋器在評估數組之前不會抱怨，因此定期測試遊戲是絕對必要的，特別是任何與數組相關的部分，否則您可能會發現您會收到一些相當憤怒的玩家的回饋。

您可以將數組條目像常規變數一樣對待，這意味著您可以在 if 語句和循環以及可以對普通變數執行的任何其他操作中使用它，唯一的區別是您在數組中指定索引而不是唯一的變數名稱。

您可能還擁有具有多個維度的數組，以便製作諸如 xy 網格甚至 xyz 之類的東西。例如，如果您想製作一個棋盤，您可以執行以下操作：

int[][] 棋盤；

多維數組，簡單來說就是數組的數組。目前，數組使用有限制，導致您無法指定多維數組中每個維度的大小。例如，要對我們的棋盤執行此操作，我們必須手動調整第一個維度的大小，然後使用循環調整第二個維度中每個條目的大小，如下所示：

棋盤.調整大小（8）；
對於（int i = 0; i <8; i ++）
{
棋盤[i].調整大小（8）；
}

此時，我們有一個名為 chessboard 的數組，其大小為 8 x 8。請注意，您不能將此調整大小的程式碼放在全域範圍內，它必須放在諸如 main 之類的函數中，而棋盤的實際聲明可能仍然是全域的。

若要存取具有多個維度的陣列中的元素，您可以使用與先前存取元素時完全相同的方法，只需在括號之間指定不同的維度。例如，為了存取棋盤左上角的方塊，可以這樣寫：

棋盤[0][0]=5;

若要存取左下角的方塊，請執行下列操作：

棋盤[0][7]=5;

三維板也是如此。如果你想要一個 3 x 3 x 3 的棋盤，你可以像這樣宣告和初始化它：

int[][][] 板；
板.調整大小（3）；
對於（int i1 = 0；i1 <3；i1 ++）
{
板[i1].調整大小（3）；
對於（int i2 = 0；i2 <3；i2 ++）
{
板[i1][i2].調整大小(3);
}
}

然後你可以透過以下方式存取一個元素：

板[2][0][1]=10；

例如，透過使用三維數組，您可以表示具有 x、y 和 z 座標的網格。這將是一個真正的 3D 遊戲，具有左、右、後、前、上、下的移動功能。

目前，BGT引擎支援最多4維的陣列。

數組可用於許多不同的事情。事實上，字串本身就是一個數組，儘管從外部看起來並非如此。字串數組的每個元素都是一個字元。
以下是一個例子：

string my_string="這是一個字串。";
alert("my_string","my_string 條目編號 2 是字母 "+my_string[2]);

這將返回字母 i。請記住，陣列總是以 0 開始，因此條目 2 保存字元 3。
正如字串可以用作函數參數和返回值一樣，數組也可以。但是有時您可能不知道數組的長度，因為它是傳回的字串或數組，或者因為循環總是根據某些條件改變值。沒問題。數組具有兩個特殊函數，可讓您檢查或更改數組。長度是不言而喻的。它傳回數組的長度。請注意，這並不意味著最終的元素編號。如果您使用此方法作為循環條件，則需要檢查計數器是否小於長度，如下例所示，它將在訊息方塊中顯示字串的每個字元：

字串 my_string="字串";
對於（uint my_counter = 0；my_counter < my_string.length（）；my_counter ++）
{
警報（“我的字串”，“我的字串[”+我的計數器+“]=“+我的字串[我的計數器]”）；
}

請注意，要獲得長度，我們說 my_string.length()。這是因為函數 length() 儲存在變數內。這被稱為一種方法。我們將在下一章更詳細地討論方法。您還會注意到，我使用了 uint 類型而不是 int 類型的變數。這是因為 length 方法傳回的是 uint，而不是 int 或 double。始終建議有符號和無符號整數匹配，否則當您嘗試執行腳本時編譯器將發出警告。
有一個最終方法用於控制數組。它被稱為調整大小，用於（你猜對了）調整陣列大小，添加新元素或刪除元素。它接受一個參數，即新數組應包含的元素數量。請注意，與長度一樣，您聲明的是應存在的實際元素數量，而不是最後一個元素的數量。例如：

string my_string="這是一個字串。";
我的字串.調整大小（4）；

這會將陣列大小調整為 4 個元素，以條目 3 結束。請注意，當您將陣列大小調整為小於其原始值時，條目將從右到左刪除，即從最後一個元素向後刪除。因此結果字串將是“this”。

可以在定義數組時準確指定哪些值將進入數組。為此，只需在數組名稱後面放置一個等號，然後在括號中跟上一個值列表。這聽起來比實際情況要複雜得多。例如，讓我們定義一個整數數組並用一些有意義的值初始化它，所有這些都在同一行程式碼中：

uint[] 質數 = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 };

數組有一個小小的限制，那就是它們只能儲存相同資料類型的值。例如，您不能有一個條目的值為 45，而另一個條目的值為「hello」。然而，這個問題可以透過另一種類型的變數（稱為字典）來解決。字典就是所謂的有方法的對象，我在上一個例子中簡單地提到它。下一章將對此進行更多討論。

字典是宣告變數的另一種方式。但是，與標準變數不同，您可以使用字串宣告變數名，並且它可以儲存任何值。因此，您可以擁有一個包含多個變數的字典，所有變數都有一個名稱和一個值，例如我們在上面的範例中描述的板。現在讓我們用同樣的例子，這次使用字典。

字典板；
對於（int x = 0; x <50; x ++）
{
板.設定（x，2）；
}

再一次，有很多新的事情需要討論。第一行只是聲明了一個字典，我們稱之為board。下一行開始一個循環，就像我們對陣列所做的那樣，從 0 到 49。下一行告訴解釋器在字典中建立一個條目，稱為 x 的當前值，並將數字 2 指派給該條目。
這比數組有一定的優勢，因為理論上你可以儲存 x 和 y 座標的值，如下所示：

字典板；
對於（int x = 0; x < 4; x ++）
{
對於（int y = 0; y < 4; y ++）
{
棋盤.設定（x+“，”+y，2）；
}
}

我們透過使用巢狀循環來增加兩個計數器並為每個方塊分配初始值 2 來實現這一點。請注意，我們在名稱字串中使用了兩個用逗號分隔的值。這只是讓我們更容易將方塊彼此分開，因為字典物件只是將其視為一個字串；它不知道這個名字對我們意味著什麼。實際上，所發生的一切就是組裝一個具有唯一名稱的字串，也就是說用逗號分隔的平方數。

類似地，要檢索某個方塊的值，您可以執行以下操作：

int 平方值；
棋盤.取得(“2,2”,平方值);

get 方法的第二個參數是透過引用傳遞的，這表示該值儲存在我們傳遞給它的變數中。如果我的意思不清楚，請參閱包含有關參數使用詳細資訊的函數部分。

除了能夠設定和檢索字典中的值之外，您還可以更改甚至刪除它們：

如果（棋盤存在（「2,2」））
{
董事會.刪除(“2,2”);
}

這告訴解釋器在名為 board 的字典中尋找“2,2”。如果找到，則刪除該值。標準變數無法做到這一點。

檢查字典條目是否存在非常重要。如果刪除或檢查不存在的條目，這不會導致任何語法或執行時間錯誤，但您嘗試用來檢索值的變數不會改變。

您也可以使用 delete_all 方法從字典中刪除所有分配。

請注意，字典比數組和常規變數慢得多，因此請謹慎使用它們。
10. 物件和類別
使用 BGT 引擎製作遊戲時，物件是一個非常強大且絕對必要的功能，因此請務必仔細閱讀本章。物件是一個常規變量，但具有特殊的功能。您可以將物件視為一個變量，它本身包含其他變量，以及對該特定物件執行某些操作的函數。物件的另一個名稱是類別。為了使事情更清楚一點，我們將從一個例子開始。

聲音氛圍；
氛圍.載入（“curry.ogg”）；

這個例子做了幾件事。它告訴引擎我們想要一個新的聲音類型對象，我們希望將該對象分配給一個稱為“氛圍”的變量，最後它調用“氛圍”變量內的函數，指定要打開的聲音的文件名。

當建立一個物件時，它可以採用一系列參數，這些參數在很大程度上決定了該物件的行為方式。物件也可能完全不採用任何參數，聲音和計時器就是很好的例子。計時器物件將如下建立：

定時器跳躍時間；

一旦創建了對象，您就可以開始使用它。物件具有所謂的屬性，屬性基本上是儲存在物件內部的變量，用於在物件的生命週期內取得和設定各種資訊。例如，聲音物件具有稱為音量的屬性。這決定了聲音的播放音量，並且可以即時修改。以下是一個範例，說明如何建立一個以流形式開啟的聲音對象，然後如何將其音量設定為 -6 分貝，也就是大約為原始振幅的一半：

聲音氛圍；
氛圍.流（“curry.wav”）；
氛圍.音量=-6；

很簡單，對吧？當然，我們也可以檢查物件中屬性的值，如下所示：

如果（環境音量> -10）
{
// 做一些事情
}

在這種情況下，如果音量大於 -10 分貝，我們就會採取一些措施。有些屬性無法修改，只能檢查，而其他屬性只能在特定時間修改，而不能在其他時間修改。您將在物件參考中每個物件的屬性清單中找到有關此內容的所有資訊。

物件還具有所謂的方法。方法只是一個函數，就像您在 BGT 引擎中找到的任何其他函數一樣，唯一的區別是它專門用於特定物件。要返回聲音對象，有一個名為 play_wait 的方法，它可以啟動聲音播放，然後等待它完成後再返回。事實上，我們之前嘗試過的 load 和 stream 函數也是很好的方法範例。
讓我們擴展前面的例子，包括播放聲音以及設定其音量。

聲音氛圍；
氛圍.流（“curry.wav”）；
氛圍.音量=-6；
氛圍.play_wait()；

熟悉的？很好，因為我們基本上只是呼叫一個函數，但它對聲音物件的特定副本進行操作，在本例中稱為氛圍。 play_wait 不接受參數，但許多其他物件方法接受參數，在這種情況下，它們在左括號和右括號之間指定，就像在常規函數呼叫中一樣。

完全可以製作同一物體的兩個版本，在這種情況下，它們將完全獨立地工作。例如，如果您製作兩個聲音對象，然後對兩個對像都呼叫播放函數，那麼您將同時播放兩個聲音。

您可能希望在您自己的各種函數中傳遞物件。要做到這一點，必須傳遞句柄，而不是物件本身。句柄本質上是對原始物件的引用。為此，您可以執行以下操作：

聲音@play_sound（字串檔名）
{
聲音；
the_sound.load（檔案名稱）；
聲音.播放()；
返回聲音；
}
空主（）
{
show_game_window("我的遊戲");
聲音@ambience = play_sound（“curry.wav”）;
氛圍.音量=-6；
@ambience=空；
}

簡而言之，當你宣告一個物件句柄時，你會在變數類型後面放置一個@（at）符號。然後，您可以像使用標準物件一樣使用這個變量，唯一的區別是您引用的是原始變數。當您想要更改句柄的值時，例如使其指向相同類型的另一個對象，只需在變數名稱前面放置 @（at）符號。
您可以將其值設為空來銷毀該句柄。這是等同於 0 的對象。考慮到這一點，雖然您無法手動銷毀物件的內容，但解決此問題的一種方法是將所有全域物件設為句柄，並且僅在函數中使用物件本身。這樣，雖然物件本身是局部變量，但它們仍然有全域變數來引用它們。因此該物件將始終保持可用，直到它的最後一個句柄被銷毀。

您也可以建立自己的物件（也稱為類別），具有自己的方法和屬性。例如，你可以為敵人創建一個類，如下所示：

階級敵人
{
健康；
int 速度；
int 位置；
無效 fire_weapon()
{
//此處為武器代碼
}
無效移動（int方向）
{
//此處放置行動程式碼
}
}

這裡唯一的新行是第一行，但它與宣告變數相同，略有不同，即您必須告訴引擎如何建立該類別。在這種情況下，我們的敵人類別有三個屬性，健康，速度和位置，以及兩種方法，fire_weapon和move。但是，我們不需要直接更改位置屬性，因為移動函數將為您執行此操作，以及您在其中輸入的任何其他程式碼。因此，記錄哪些方法和屬性應該或不應該被觸及非常重要。
要使用我們的類，我們可以這樣寫：

敵方機器人；

然後我們的類別就可以使用了，就像 BGT 中提供的物件一樣。

機器人.移動（右）； //假設右邊是一個常數
機器人.fire_weapon（）； //讓機器人發射武器

但還有一個小問題。敵人的健康值尚未被賦予。這意味著每次我們聲明一個敵人變數時，我們都必須將每個生命值設為 100。這可以透過建構函式和析構函式輕鬆解決。
建構函數只是指示引擎如何設定我們的屬性，和/或執行我們在創建類別的新實例時可能想要完成的任何其他任務。建構函數是可選的，並且以與類別相同的名稱聲明。當我們的類別實例被銷毀時，析構函數被呼叫。它可用於完成任何必要的清理工作。析構函數也是可選的，其宣告方式與建構子類似，但在類別名稱前面帶有 ~（波浪號）符號。最後，建構函式和析構函式是唯一沒有宣告回傳類型的函式；甚至不是無效的。
讓我們擴展敵人類別以包含一個建構函數：

階級敵人
{
健康；
int 速度；
int 位置；
敵人（）
{
健康=100；
位置=0；
速度=300；
}
無效 fire_weapon()
{
//此處為武器代碼
}
無效移動（int方向）
{
//此處放置行動程式碼
}
}

現在，每次我們聲明一個敵人類型的變數時，它的屬性總是會被正確設定。

請注意，也可以這樣寫：

階級敵人
{
int 健康=100；
int速度=300；
int位置=0；
無效 fire_weapon()
{
//此處為武器代碼
}
無效移動（int方向）
{
//此處放置行動程式碼
}
}

在這種情況下，我們實際上並沒有實作建構函數，因為屬性在其宣告中直接被賦予了預設值。然而，構造函數可能需要做其他工作，所以我們將來也會堅持使用它。

為了使您的類別更具可移植性，您可以為建構函數使用參數，而不是期望類別的使用者直接修改屬性。我們透過快速增長的敵人階層來證明這一點：

階級敵人
{
健康；
int 速度；
int 位置；
敵人（int init_health，int init_speed，int init_pos）
{
健康=初始化健康；
位置=init_pos；
速度=初始速度；
}
無效 fire_weapon()
{
//此處為武器代碼
}
無效移動（int方向）
{
//此處放置行動程式碼
}
}

現在為了宣布我們的敵人，我們將寫下以下內容：

敵方機器人（100，300，0）；

這會將敵人物件實例的屬性設定為我們指定的值。這樣，您就可以創建具有不同力量和速度且隨機分散在遊戲板上的敵人。請注意，由於我們只定義了一個接受參數的建構函數，因此如果不指定這些參數，就無法再聲明我們的敵人（有關自動預設建構函數的更多信息，請參閱下文）。

正如我在函數部分提到的，函數可以有相同的名稱，只要參數不同。對於類別方法也同樣如此，包括建構函數，但不包括析構函數。這意味著，如果您想要讓您的敵人被設定不同的值，您可以有一個使用者必須指定參數的建構函數，但您也可以有一個不需要給出參數的建構函數。這樣，您就可以決定是否使用參數來聲明您的敵人。然後，編譯器將找到與您的參數相符的建構函式（如果有），並為您呼叫它。我們現在將使用敵人代碼來演示這一點。我不會包含類別屬性或方法，我只會使用我們的建構子。

階級敵人
{
//屬性放在這裡
//無參數建構函數
敵人（）
{
健康=100；
位置=0；
速度=300；
}
//帶參數的建構子：
敵人（int init_health，int init_speed，int init_pos）
{
健康=初始化健康；
位置=init_pos；
速度=初始速度；
}
//類別方法放在這裡
}

當然，你可以把你的類別方法放在你喜歡的任何位置，但是把我們的建構函數放在類別的頂部是合乎邏輯的，因為這是它呼叫的第一個函數。
現在聲明我們的敵人，因為我們有兩個建構函數，我們可以這樣做

敵方機器人；

或者

敵方機器人（200，150，0）；

第一個聲明只會給我們一個具有其預設值的敵人。然而，我們的第二次宣言將使敵人的力量和速度比第一次增加兩倍。雖然 150 是 300 的一半，但我們告訴程式他移動所需的時間，而不是他在給定時間內移動的速度。

如果您沒有在腳本中定義建構函數，編譯器會自動為您建立一個不帶任何參數的空建構函數。這意味著預設情況下，我們可以聲明任何類，如上所示，沒有參數列表。但是，如果定義一個接受參數的建構函數，則預設建構函數將不再自動實作。如果您根本沒有指定建構函數，它只會由腳本編譯器自動實作。因此，如果您想要一個預設建構函數（也就是說不帶參數的建構函數）以及一個帶有參數的建構函數，那麼您必須像上面的例子一樣明確地實現這兩個建構子。

請注意，如本教學前面所提到的，如果您沒有明確賦予原始變數（即不是物件的變數）初始值，它們將包含未定義的值。這不僅適用於全域變數和函數和方法內部的變量，也適用於類別中的屬性。如果你聲明一個如下物件：

敵方機器人；

然後，預設構造函數（如果存在）將被隱式調用，因此與 int 和 float 等原始變數不同，機器人變數的內容根本不是未定義的。然而，建構子應該初始化類別內部使用的屬性。這並不會自動發生。簡而言之，您必須確保在使用之前將類別中使用的所有屬性初始化為有意義的值 - 就像對類別之外的原始變數所做的那樣。構造函數是執行此操作的良好位置。或者，您可以在聲明屬性時為其賦予初始值，就像對待全域變數或局部函數變數一樣。

析構函數的工作方式與建構子大致相同，只是它們是在你的類別被銷毀時被呼叫的。這通常是當它的最後一個引用被銷毀時，或者當程式退出時。這再次是我們的基本敵人，它有一個不帶參數的建構子和一個析構函數：

階級敵人
{
//屬性放在這裡
敵人（）
{
健康=100；
位置=0；
速度=300；
}
~敵人()
{
alert("榮耀！","你的敵人死了！");
}
//方法放在這裡
}

運行這個例子是沒有意義的。事實上，如果你將此範例實現到遊戲中，一旦你退出遊戲，你就會收到左右中間的消息框，告訴你你的敵人已死，因為解釋器正在銷毀所有活動對象並因此調用它們的析構函數（如果存在）。與建構函數一樣，如果您選擇不自行建立析構函數，編譯器將預設為您建立一個空的析構函數。這意味著如果您想在類別被銷毀時執行某些特定的操作，則只需要明確定義一個。
11. 高階物件技術
上一章已經為你提供了足夠的信息，讓你可以在遊戲創作過程中有效地使用物件。本章將擴展您的物件導向技術庫。請注意，這裡介紹的概念被認為是高級的，您很可能決定在第一次閱讀本教學時跳過本章。

11.1.對象簡介
在我們深入研究高級概念之前，讓我們先簡單盤點一下您已經了解的有關對象的知識。以下是物件導向術語的簡要概述。如果有任何不清楚的地方，您可能想要重新閱讀上一章，因為它為本章奠定了基礎。

我們可以把物件想像成一個盒子，裡面有變數和函數。物件內部的變數稱為屬性，物件內部的函數稱為方法。

每個物件都屬於一個類別或類型，而物件的類別決定了該物件具有哪些屬性和方法。例如，屬於聲音類別的聲音物件將始終具有播放方法，因為聲音類別是這麼說的。

每當建立一個物件時，就會執行一個稱為建構函數的特殊函數。同樣，每當一個物件被銷毀時，就會執行一個稱為析構函數的特殊函數。建構子和析構函式由物件的類別定義。一個類別可以提供多個具有不同參數的建構函數。但是，一個類別不允許有多個析構函數。
11.2.遺產
想像一下，您正在創建一款遊戲，其中玩家角色可能會遇到不同種類的動物，並且我們假設您已經編寫了一個類別來代表遊戲中的鳥類。鳥類類別可能看起來像下面這樣：

類鳥
{
空行（）
{
// 鳥類行走代碼在此。
}
無效運行（）
{
// 此處為鳥類的運行程式碼。
}
無效飛行（）
{
// 鳥類飛行的程式碼在這裡。
}
}

// 讓我們創造一些鳥
空主（）
{
鳥麻雀；
鴿子
鳥鷹；
麻雀. 運行();
鴿子.行走()；
鷹.飛行(); // 帶我們去我們所屬的地方 :-)
}


現在，為了讓遊戲更加逼真，我們想出了這樣的想法，即上述程式碼中的麻雀實際上應該能夠唱歌。我們該如何處理這個問題？

一種方法可能是透過添加 sing 方法來修改我們的鳥類。顯而易見的問題是，這將賦予每隻鳥（而不僅僅是麻雀）唱歌的能力。即使在遊戲世界裡，鴿子仍會咕咕叫，鷹仍會哭泣。畢竟，正是寫實主義促使了唱歌方法的加入。

我們的第二種方法是為麻雀創建一個額外的類，該類在各個方面都與鳥類類相同，但有一點除外：它有一種唱歌的方法。以下是這兩個類別的完整對應程式碼：

類鳥
{
空行（）
{
// 鳥類行走代碼放在這裡。
}
無效運行（）
{
// 此處為鳥類的運行程式碼。
}
無效飛行（）
{
// 鳥類飛行的程式碼在這裡。
}
}

麻雀班
{
空行（）
{
// 麻雀的行走代碼放在這裡。
}
無效運行（）
{
// 此處為麻雀的運行程式碼。
}
無效飛行（）
{
// 麻雀的飛行代碼在這裡。
}
無效唱歌（）
{
// 麻雀唱歌的程式碼放在這裡。
}
}


這種方法確實解決了我們的問題，因為現在麻雀可以唱歌，但一般鳥類不能。但也存在一些缺點：

1. 我們的程式碼變得冗長且重複。
2.我們的程式碼變得更難維護。想像一下，您想修復所有鳥類的飛行方法中的一個錯誤。在我們的運行範例中，您需要更改兩種飛行方法，一種用於鳥類，另一種用於麻雀類。修復一個問題很容易，但很容易忘記修復另一個問題。在這種情況下，即使您確信已經修復了錯誤，但您很快就會發現它仍然存在。更糟的是，你的客戶可能會發現這一點。現在假設您已經以相同的方式為咕咕叫的鴿子和哭泣的鷹創建了類別。然後必須在四個地方修復 fly 方法中的錯誤。只要忘記一個，錯誤就會繼續存在。測試甚至可能表明它已被修復，因為漏洞只出現在某些鳥類身上，而不會出現在其他鳥類身上。
3. 假設你的遊戲最終包含一組鳥類，如下：
鳥[] 鳥舍(50);
你無法將麻雀放入鳥舍，因為陣列的所有元素必須共享一個通用的資料類型。
如果我們有辦法為麻雀定義一個類，它繼承了鳥類類的所有功能並簡單地添加了唱歌方法，那不是很棒嗎？如果 BGT 能夠以某種方式知道麻雀只是另一種鳥，以便我們可以將麻雀放入鳥類數組中，那不是更好嗎？您會很高興知道 BGT 中存在這樣的方式。這被稱為類別繼承。

假設我們已經按照上述方法定義了鳥類類別。現在我們可以定義麻雀類別如下：

麻雀科：鳥類
{
無效唱歌（）
{
// 麻雀唱歌的程式碼放在這裡。
}
}

// 讓我們來製作一些鳥
空主（）
{
鳥b1；
鳥b2；
麻雀;
s.飛行（）； // 有效，因為每隻麻雀也是一隻鳥
s.唱歌（）； // 有效，因為 s 是麻雀
鳥@[] 鳥舍 = { b1, b2, s };
}


在上面的程式碼中，麻雀類繼承自鳥類。我們稱麻雀為衍生類或子類，稱鳥為基類或超類。這些都只是表達相同事實的不同方式，我們可以隨意互換使用它們，因為它們都是常用的。

若要定義衍生類，請在類別名稱後面加上冒號，然後在其後寫上基類的名稱，如上所示。衍生類別將繼承基底類別中定義的所有屬性和方法。就好像每個麻雀物件都包含一個隱藏的鳥物件。畢竟，作為一隻麻雀，它的一部分就是作為一隻鳥。順便說一句，這個類似禪宗的說法包含了整個繼承的概念。

值得注意的是，我們的鳥舍是一排鳥柄，而不是一排鳥。當我們把麻雀當作鳥來對待時，使用手柄至關重要。考慮以下程式碼：

麻雀; // 創造了一隻麻雀
鳥@ b1 = s; // 透過句柄b1，我們可以把s當成一隻鳥
b1.飛行（）； // 這有效，相當於說 s.fly();
s.唱歌（）； // 這是可行的，因為 s 是一隻麻雀
b1.唱歌（）； // 不起作用，因為雖然 b1 指向一隻麻雀，但我們把它當作一隻鳥
鳥 b2 = s; // 製作一隻新鳥


上面的最後一行其實比你想像的要複雜得多。它根據 s 中包含的數據創建了一隻新鳥 b2，但 b2 不會是一隻麻雀。你可能會說 b2 包含 s 的鳥性但不包含麻雀性。這與 b1 截然不同，因為 b1 不是 s 的副本，而只是一個句柄，透過它可以將 s 視為一隻鳥。 b1 和 s 是記憶體中同一物件的名稱，但 b2 卻完全是另一回事。

衍生類別不僅可以為基底類別新增方法，還可以修改現有方法的行為。假設我們想賦予麻雀自己獨特的飛行方法。麻雀類看起來如下：

麻雀科：鳥類
{
無效唱歌（）
{
// 麻雀唱歌的程式碼放在這裡。
}
無效飛行（）
{
// 麻雀的飛行代碼在這裡。
}
}

空主（）
{
鳥b; // 創造一隻鳥
麻雀; // 創造一隻麻雀
鳥@[] 鳥舍 = { b, s }; // 將兩個鳥舍的句柄放入同一個鳥舍
鳥舍[0].飛行()； // 呼叫 bird 類別的 fly 方法
鳥舍[1].飛行()； // 呼叫 sparrow 類別的 fly 方法
}


這裡我們透過在衍生類別中定義一個同名且具有相同參數（在本例中為無）的方法來修改方法的行為。我們說麻雀類的 fly 方法涵蓋了鳥類中的 fly 方法。

注意繼承如何提供一種對不同類型的物件進行平等對待的方法。對我們的鳥舍來說，麻雀只不過是普通的鳥而已。這個概念稱為多態性。這個字源自於希臘語，意思是「不同形式」。在這種情況下，它指的是我們平等對待形式、類型或類別不同的物件。在我們正在運行的範例中，多態是可能的，因為我們的兩個類，鳥和麻雀，透過繼承相關。當然，我們不能把任意的鳥放入麻雀數組中，原因很簡單，不是每隻鳥都是麻雀。相反，我們既不能將聲音物件儲存在鳥類數組中，也不能將鳥類物件儲存在聲音數組中，因為鳥類和聲音是無關的。

雖然屬性和方法是可以繼承的，但是建構子不能。但是由於每個麻雀對像都包含一個隱藏的鳥對象，因此在創建麻雀時需要執行兩個構造函數。就像房屋從地基開始向上建造一樣，首先執行的是基類構造函數。當物體被毀壞時則相反。在這種情況下，析構函數從衍生類別向上調用，因此 sparrow 析構函數將首先出現。你可以透過提醒自己毀滅某物與創造某物相反來記住這一點。

另一種看待它的方式是想像衍生類別建構函式所做的第一件事就是呼叫無參數的基底類別建構子。這是自動發生的，並且在大多數情況下正是您想要的。然而，在某些情況下，您寧願呼叫基底類別中的另一個建構函式。對於這種情況，BGT 有一個特殊的關鍵字，稱為 super。您可以像函數名稱一樣使用 super 關鍵字，只是它指的是基底類別的建構子。

假設鳥類有一個額外的構造函數，它將鳥的翼展作為參數：

類鳥
{
雙翼展；
鳥（雙翼展）
{
this.wingspan = 翼展;
}
空行（）
{
// 鳥類行走代碼放在這裡。
}
無效運行（）
{
// 此處為鳥類的運行程式碼。
}
無效飛行（）
{
// 鳥類飛行的程式碼在這裡。
}
}


上面的程式碼使用了您可能以前沒有見過的“this”關鍵字。在類別定義主體中的任何地方，都可以使用關鍵字「this」來指涉目前考慮的物件。如果是建構函數，這當然是目前建立的物件。在上面的例子中，關鍵字是必要的，因為名稱 wingspan 可能會引用兩個東西，其中一個是需要建立的物件的屬性，另一個是建構函數參數。如果我們只是寫了
翼展 = 翼展；
那麼我們將把名為 wingspan 的建構子參數賦給它自己的值，這是一個有效但毫無意義的操作。

現在我們可以修改 sparrow 類別以便它呼叫我們的新建構子：

麻雀科：鳥
{
麻雀（）
{
超級（3）； // 呼叫參數為 3 的基礎建構函數
}
無效唱歌（）
{
// 麻雀唱歌的程式碼放在這裡。
}
}


透過這種方式，我們指定了麻雀的翼展始終為 3。這種方法的優雅之處在於我們不必直接接觸翼展屬性，而是可以使用基底類別中存在的程式碼。現在您可能已經意識到，程式碼重用就是物件導向程式設計的全部內容。

讓我們添加鷹和鴿子的類別只是為了證明它是多麼簡單。在此過程中，我們將為每隻鳥提供一種 make_sound 方法，使其發出其獨特的聲音。以下是我們的完整程式碼：

類鳥
{
雙翼展；
鳥（雙翼展）
{
this.wingspan = 翼展;
}
空行（）
{
// 鳥類行走代碼放在這裡。
}
無效運行（）
{
// 此處為鳥類的運行程式碼。
}
無效飛行（）
{
// 鳥類飛行的程式碼在這裡。
}
無效 make_sound()
{
// 此處為通用鳥類聲音的代碼。
}
}

麻雀科：鳥類
{
麻雀（）
{
超級（3）；
}
無效唱歌（）
{
alert("多美啊！", "麻雀唱著小曲。");
}
無效 make_sound()
{
唱歌（）; // 可以使用「this」關鍵字，但沒必要
}
}

鷹類：鳥
{
鷹（）
{
超級（20）；
}
無效哭泣（）
{
alert("好有氣氛！", "最後一隻鷹在最後一座崩塌的山峰上哭泣。");
}
無效 make_sound()
{
哭（）;
}
}

鴿子類：鳥類
{
鴿子（）
{
超級（8）；
}
無效 coo（）
{
alert("多麼令人不安！", "一隻鴿子用只有鴿子才覺得可愛的語言咕咕地唱著它的情歌。");
}
無效 make_sound()
{
酷（）；
}
}

// 讓我們來製作一些鳥
空主（）
{
麻雀;
鷹e;
鴿子 d;
鳥@[] 鳥舍 = { s, e, d };
對於（uint i = 0；i <aviary.length（）；i ++）
{
鳥舍[i].飛行()；
鳥舍[i].make_sound()； // 各按其類
}
}


您可能希望運行此程式碼來觀察多態性的實際作用。請注意我們的 for 循環如何告訴每隻鳥發出其特有的聲音，並且每隻鳥的行為都非常不同。如果稍後我們在鳥舍中加入另一種鳥，則 for 迴圈可以保持不變。這導致了一個有趣的觀察：舊程式碼可以呼叫新程式碼。

工具只有在懂得如何使用且知道何時使用的人手中才真正有用。對於繼承，一個好的方法如下：當不使用繼承時，使用繼承會要求您維護相同程式碼的幾個相同副本或類似變體。在對本身俱有層次結構的現實世界概念進行建模時使用繼承。最後，當對非常相似但不完全相同類型的物件給予平等對待時，請使用繼承。透過一些練習，您將在設計階段早期甚至在編寫第一行程式碼之前就認識到這些情況。本段所討論的藝術和科學被稱為物件導向設計，以防您想對此進行一些研究。

工具在懂得何時使用並且知道何時不使用的人手中會發揮更大的作用。因此，我們以對繼承的警告來結束本節。雖然它確實是具有多種有效用途的強大技術，但是許多程式設計師在第一次了解它時，就被它的優雅所陶醉，並得出結論，找到正確的類層次結構一定是解決軟體設計中所有可能問題的解決方案。如果您不小心，您的類別層次結構可能會超出合理的限制，因為您希望它們覆蓋幾乎所有可以想像到的子類別。例如，我們可能為科學界所知的每一種鷹或鴿子定義單獨的類別。 BGT 會毫無怨言地讓我們將類別層次結構擴展到任意深不可測的深度。但除非我們的遊戲是關於鳥類學（研究鳥類的動物學分支），否則上面提出的兩級層次結構可能已經足夠了。說實話，在大多數情況下，即使單一鳥類也足夠了。或引用 Python 發明者 Guido van Rossum 的話：“簡單勝過複雜。複雜勝過繁瑣。”
11.3.接口
在上一節中，您了解了稱為多態性的強大概念。實現多態性的一種方法是透過繼承，因為 BGT 允許我們透過其基底類別的句柄來引用物件。當類別共享大量程式碼或大量概念相似性時，繼承是有意義的。例如，麻雀和鴿子擁有行走、飛翔和奔跑的所有代碼，此外，由於它們都是鳥類，因此在概念上也很相似。

然而，可以設想這樣的場景：物件既不共享大量程式碼，也不共享大量概念特徵，但仍然可以得到平等對待。考慮聲源的概念。查看上一節的最後一個程式碼範例，你會發現鳥可能被稱為聲源。畢竟它有一個 make_sound 方法。現在讓我們發明另一種聲源，幾乎與任何鳥類都完全不同的聲源。

樂器類
{
單位複雜度；
樂器（單位複雜度）
{
這個.複雜性 = 複雜性；
}
無效 make_sound()
{
alert("訊息", "您聽到了音樂的聲音。");
}
}

鼓類：樂器
{
鼓（）
{
超級（2）；
}
無效 make_sound()
{
alert("Info", "您聽到了穩定的鼓聲。");
}
}


鳥類和樂器在各方面都有不同，但有一點除外——它們都是聲源。但僅憑這種相似性就值得對它們給予平等對待，例如透過在聲源數組中儲存它們的句柄。

實現這一點的一種方法是將 bird 和 musical_instrument 作為衍生類，並使用我們稱為 sound_source 的共同基底類別。在上一節中，我們制定了一些關於繼承何時可能有用的標準。讓我們看看它們是否適用於鳥類和樂器：

當不使用繼承時，使用繼承會要求您維護相同程式碼的幾個相同副本或類似變體。事實當然不是這樣，因為 bird 和 musical_instrument 除了一個共同的方法名稱之外，沒有任何程式碼共享。

在對本身俱有層次結構的現實世界概念進行建模時使用繼承。這顯然是一個邊緣案例。有人可能會說，由於鳥和樂器都是聲源，因此這兩個概念之間存在某種層次結構。然而，在實踐中，聲源的概念相對於樂器和鳥類的概念來說太抽象了，以至於我們在日常思維中無法感知到這種層次結構。

最後，當對非常相似但不完全相同類型的物件給予平等對待時，請使用繼承。請注意“非常相似”這個表達。在日常話語中，鳥類和樂器甚至被認為毫不相似。

儘管我們盡了最大努力，但似乎無法為繼承提供良好的理由。但是，對於在其他所有方面都不相似的類型，還有另一種表達在某一方面相似的方式。

一隻鳥和一件樂器共享一個共同的方法簽名。簽名這個字指的是名稱和參數的組合，所以我們的意思是兩個類別都有同名且具有相同參數的方法，在本例中沒有參數。另一種說法是，bird 和 musical_instrument 實作了相同的接口，該接口由一個名為 make_sound 的無參數方法組成。以下是定義該介面的程式碼：

介面聲音來源
{
無效的make_sound（）;
}


請注意 make_sound 後面括號後面的分號。在介面定義中我們不包含方法體。介面只是方法簽章的列表，如果類別包含具有給定介面簽章的方法，我們就說該類別實作了該介面。關鍵在於可以透過物件某個介面的句柄來操作物件。

我們必須告訴 BGT，bird 類別和 musical_instrument 類別都實作了 sound_source 介面。它的語法與繼承完全相同，因此
類鳥
我們寫
鳥類：聲源
而不是
樂器類
我們寫
樂器類：聲源

以下是關於繼承和介面所學內容的程式碼範例：

介面聲音來源
{
無效的make_sound（）;
}
鳥類：聲源
{
雙翼展；
鳥（雙翼展）
{
this.wingspan = 翼展;
}
空行（）
{
// 鳥類行走代碼放在這裡。
}
無效運行（）
{
// 此處為鳥類的運行程式碼。
}
無效飛行（）
{
// 鳥類飛行的程式碼在這裡。
}
無效 make_sound()
{
// 此處為通用鳥類聲音的代碼。
}
}

class sparrow : bird // 注意，這會自動實作 sound_source
{
麻雀（）
{
超級（3）；
}
無效唱歌（）
{
alert("多美啊！", "麻雀唱著小曲。");
}
無效 make_sound()
{
唱歌（）; // 可以使用「this」關鍵字，但沒必要
}
}

鷹類：鳥
{
鷹（）
{
超級（20）；
}
無效哭泣（）
{
alert("好有氣氛！", "最後一隻鷹在最後一座崩塌的山峰上哭泣。");
}
無效 make_sound()
{
哭（）;
}
}

鴿子類：鳥類
{
鴿子（）
{
超級（8）；
}
無效 coo（）
{
alert("多麼令人不安！", "一隻鴿子用只有鴿子才覺得可愛的語言咕咕唱著它的情歌。");
}
無效 make_sound()
{
酷（）；
}
}

樂器類：聲源
{
單位複雜度；
樂器（單位複雜度）
{
這個.複雜性 = 複雜性；
}
無效 make_sound()
{
alert("訊息", "您聽到了音樂的聲音。");
}
}

鼓類：樂器
{
鼓（）
{
超級（2）；
}
無效 make_sound()
{
alert("Info", "您聽到了穩定的鼓聲。");
}
}

空主（）
{
麻雀;
鷹e;
鴿子 d;
鳥@[] 鳥舍 = { s, e, d };
對於（uint i = 0；i <aviary.length（）；i ++）
{
鳥舍[i].飛行()；
鳥舍[i].make_sound()； // 各按其類
}
鼓 我的鼓；
聲音來源@[] 我的聲音來源 = { d， s， 我的鼓 };
對於（uint j = 0；j <my_sound_sources.length（）；j ++）
{
我的聲音來源[j].make_sound();
}
}

請注意，雖然一個類別最多可以直接從另一個類別繼承，但它可以實作任意數量的介面。如果您希望表達名為 c 的類別實作了三個介面 i1、i2 和 i3，那麼只需用逗號分隔介面名稱，如下所示：

c 類 ：i1、i2、i3
{
}


接口和繼承並不互相排斥，因此一個類別可以實作某些接口，也可以從另一個類別繼承。因此，為了表達類別 c1 繼承自類別 c2 並且實作介面 i1、i2 和 i3，可以這樣寫：

c1 類：c2、i1、i2、i3
{
}
11.4.運算子重載
本節的標題雖然聽起來很複雜，但實際上比前兩節關於繼承和介面的簡單得多。運算子重載就是程式設計師所說的「語法糖」。如果使用得當，它可以使您的生活變得更加美好，並產生非常易讀和優雅的代碼，但如果您願意的話，您也可以完全不使用它。換句話說，它是一種可選的便利功能。

讓我們寫一個簡單的類別來表示三維向量。如果你需要複習一下，三維向量只是三個數字的組合。例如，（3, 4, 5）是一個三維向量。我們說一個向量是由三個數組成，這三個數就是向量的分量。依照慣例，當處理三維向量時，我們將其第一個分量稱為 x 分量，第二個分量稱為 y 分量，第三個分量稱為 z 分量。因此 (3, 4, 5) 的 y 分量為 4。在數學中，向量通常用於描述空間中的位置。例如，（3, 4, 5）可以解釋為：從一個稱為原點的固定位置，向右走三米，向前走四米，然後向上飛五米。而且，不要開始與數學家爭論重力不允許您這樣做——畢竟，這不是物理課。再說了，我不應該把儀表帶進來。順便說一下，在遊戲中，向量可以發揮重要作用，因為它們非常適合描述事物的位置、移動的速度以及加速的速度。他們甚至可以描述某物或某人所面向的方向。所以永遠不要低估一個向量，即使它只有三個數字。

向量可以相加，相加的方法是將各個分量相加。例如，將 (3, 4, 5) 加到 (5, 4, 3) 上得到 (8, 8, 8)。

向量可以與一個稱為標量的數相乘，其乘法是將各個分量與標量相乘。例如，將 (3, 4, 5) 乘以 6 得到 (18, 24, 30)。

最後，可以比較向量是否相等。規則規定，當兩個向量的各個分量相等時，這兩個向量相等，即這兩個向量共享相等的 x、y 和 z 分量。例如，(3, 4, 5) 等於 (3, 4, 5)，但不等於 (4, 5, 3)。

讓我們將我們所知道的關於向量的知識轉化為程式碼：

測試向量類
{
雙x；
雙 y；
雙z；
測試向量（雙精確度 x，雙精確度 y，雙精確度 z）
{
這個.x = x;
這個.y = y;
這個.z = z;
}
雙精度浮點數 ()
{
返回x；
}
雙 get_y()
{
返回 y；
}
雙 get_z()
{
返回z；
}
字串到_string（）
{
返回“（” + x + “，” + y + “，” + z + “）”；
}
test_vector 新增_到（test_vector @ 其他）
{
返回 test_vector(x+other.x, y+other.y, z+other.z);
}
test_vector 乘以_標量（雙標量）
{
返回 test_vector(x*標量，y*標量，z*標量)；
}
bool is_equal_to(test_vector@其他)
{
返回 x==other.x && y==other.y && z==other.z;
}
}

空主（）
{
測試向量v1（3，4，5）；
測試向量v2（5，4，3）；
測試向量@ v3 = v1.add_to(v2);
alert("供您參考", v1.to_string() + " + " + v2.to_string() + " = " + v3.to_string());
測試向量@ v4 = v1.multiply_by_scalar(6);
alert("供您參考", "6 * " + v1.to_string() + " = " + v4.to_string());
如果（v1.等於（v2））
{
alert("供您參考", v1.to_string() + " 和 " + v2.to_string() + " 相等。");
}
別的
{
alert("供您參考", v1.to_string() + " 和 " + v2.to_string() + " 不相等。");
}
}

觀察我們用來加入兩個向量的笨拙表達式 v1.add_to(v2)。真正節省時間的方法是可以這樣定義向量的加法，也就是我們可以簡單地寫 v1+v2，然後讓 BGT 自動做對的事。這確實是可能的，稱為運算符重載。運算符是表示運算的符號。例如加號“+”代表加法。重載是擴展符號意義的過程。例如BGT中加法運算子的意思是將數字與字串相加。現在我們將擴展或重載加法運算符，使其具有添加向量的附加意義。

在BGT中重載操作符的方法是定義一個具有特殊名稱的方法。例如，為了重載加法運算符，我們將定義一個名為 opAdd 的方法；為了重載星號運算符，我們將定義一個名為 opMul 的方法。在這個過程中，我們甚至可以透過定義一個名為 opEquals 的方法來重載雙等號（==）運算子（==）。順便說一句，我們已經定義了所有這三種方法，所以我們只需重命名它們。以下是修改後的程式碼（帶有運算子重載）：

測試向量類
{
雙x；
雙 y；
雙z；
測試向量（雙精確度 x，雙精確度 y，雙精確度 z）
{
這個.x = x;
這個.y = y;
這個.z = z;
}
雙精度浮點數 ()
{
返回x；
}
雙 get_y()
{
返回 y；
}
雙 get_z()
{
返回z；
}
字串到_string（）
{
返回“（” + x + “，” + y + “，” + z + “）”；
}
test_vector opAdd(test_vector@其他)
{
返回 test_vector(x+other.x, y+other.y, z+other.z);
}
test_vector opMul（雙標量）
{
返回 test_vector(x*標量，y*標量，z*標量)；
}
test_vector opMul_r（雙標量）
{
回這個*標量；
}
bool opEquals（test_vector @其他）
{
返回 x==other.x && y==other.y && z==other.z;
}
}

空主（）
{
測試向量v1（3，4，5）；
測試向量v2（5，4，3）；
測試向量@v3=v1+v2；
alert("供您參考", v1.to_string() + " + " + v2.to_string() + " = " + v3.to_string());
測試向量@v4=6*v1；
alert("供您參考", "6 * " + v1.to_string() + " = " + v4.to_string());
如果（v1 == v2）
{
alert("供您參考", v1.to_string() + " 和 " + v2.to_string() + " 相等。");
}
別的
{
alert("供您參考", v1.to_string() + " 和 " + v2.to_string() + " 不相等。");
}
}

細心的讀者此時可能已經注意到，我透過定義一個名為 opMul_r 的方法作弊了。許多用於運算子重載的神奇方法名，例如 opAdd 或 opMul，也都有附加 _r 的對應方法名。 r 代表反向，它所做的就是告訴 BGT 我們希望重載相同的運算子但運算元交換。在這種情況下，方法 opMul 定義當對向量 v 求值類似 v*5 的表達式時發生的情況，而方法 opMul_r 定義當求值類似 5*v 的表達式時發生的情況。

我們可以根據操作數的數量對運算符進行分類。操作數是一個技術術語，表示運算子作用的值。例如，在表達式 x+y 中，我們有 + 作為運算符，x 和 y 作為運算元。

只有一個運算元的運算子稱為一元運算子。要重載一元運算符，您需要定義一個沒有參數的方法，因為唯一的操作數是物件本身。以下是兩個可重載的一元運算子的魔術方法名稱：

• opNeg 過載 -
• opCom 超載 ~
• opPreInc 重載 ++（前綴）
• opPreDec 重載--（前綴）
• opPostInc 重載 ++（後綴）
• opPostDec 重載 -- （後綴）


接下來我們討論二元運算子和一個稍長的列表。您可能已經猜到了，二元運算子是對兩個運算元進行操作的運算子。為了重載二元運算符，我們定義一個接受一個參數的方法，因為另一個操作數是物件本身。以下是魔法方法名稱的清單：

• opAdd 和 opAdd_r 重載 +
• opSub 和 opSub_r 重載 -
• opMul 和 opMul_r 過載*
• opDiv 和 opDiv_r 重載 /
• opMod 和 opMod_r 過載 %
• opAnd 與 opAnd_r 重載 &
• opOr 和 opOr_r 重載 |
• opXor 和 opXor_r 重載 ^
• opShl 和 opShl_r 重載 <<
• opShr 和 opShr_r 過載 >>
• opUshr 和 op_Ushr_r 過載 >>>


以下運算子也是二元的，但它們被認為是不同的，因為它們是所謂的 BGT 賦值運算子。賦值運算子是改變變數或屬性的值的運算子。例如，當我們寫出簡單的語句
我＝5；
我們使用了賦值運算子 =。對應列表如下：

• opAssign 重載 =
• opAddAssign 重載 +=
• opSubAssign 重載 -=
• opMulAssign 重載 *=
• opDivAssign 重載 /=
• opModAssign 重載 %=
• opAndAssign 重載 &=
• opOrAssign 重載 |=
• opXorAssign 重載 ^=
• opShlAssign 重載 <<=
• opShrAssign 重載 >>=
• opUshrAssign 重載 >>>=


接下來我們將介紹所謂的索引運算子。您可能已經了解了它的用法，只是當時您可能沒有意識到它是一個操作符。這是用於從數組中檢索元素的語法，透過在數組名稱後面跟一對括號（在括號內指定索引）來實現。例如，如果我們定義了一個名為 a 的 int 數組，則表達式 a[3] 就是使用索引運算子的一個實例。

為了重載索引運算符，我們定義了一個名為 opIndex 的方法。它將索引作為參數並傳回給定索引處的值。這是向量類別的重載索引運算子：

雙精度& opIndex(int i)
{
如果（i == 0）
{
返回x；
}
否則，如果（i==1）
{
返回 y；
}
否則，如果（i==2）
{
返回z；
}
}

關於上述程式碼需要注意的一個重要細節是回傳類型。該方法不僅返回 double，還返回一個稱為 double& 的東西。回想一下，我們之前在介紹引用的概念時討論過 & 符號。此方法傳回對雙精度型的引用。

透過引用傳回一個值是一種告訴 BGT 不要複製該值而是傳回其位置或位址的方法，就像使用了句柄一樣。然而，引用的另一個優點是它可以在作業的左側使用。這意味著我們現在可以透過以下方式更改向量 v 的 y 分量
v[1] = 42;

剩下六名操作員。它們都是二進制的，並且都計算有關兩個值之間關係的特定問題的是/否答案。這就是我們稱它們為關係運算子的原因。它們的另一個常用術語是比較運算符，因為它們通常用於比較值。

我們從相等運算子 == 和 != 開始，它們分別回答兩個值是否相等或不相等的問題。要重載這兩個運算符，我們只需要定義單一方法 opEquals。它必須只接受一個參數，也必須傳回一個布林值。

剩下的四個運算子是 <、<=、> 和 >=，它們分別回答第一個運算元是否小於、小於或等於、大於、大於或等於第二個運算元的問題。透過定義一個名為 opCmp 的方法，可以一次重載這四個方法。此方法應該只採用一個參數，並且應根據以下規則傳回一個 int 值：

•如果兩個操作數相等，則傳回零。
•如果第一個操作數小於第二個操作數，則傳回負數。
•如果第一個大於第二個，則傳回一個正數。
12. 函數句柄
正如您在前兩章中了解到的，句柄提供了一種來回傳遞值而不複製它們的方法。我們傳遞的不是實際值，而是句柄，實際上，它很像街道地址。它不是值本身，但它包含了查找該值所需的所有資訊。

確實可以儲存並傳遞函數句柄。這是一個兩步驟過程。第一步是定義您要透過句柄引用的函數類型。我們所說的函數類型和我們之前定義的簽名是同一個概念。任何兩個採用相同數量和類型的參數並傳回相同類型值的函數稱為共享相同的簽名，因此可以稱為同一種函數。

定義函數類型的方法如下：

funcdef void my_function_type()；

第二步是使用函數類型，就像使用句柄時使用任何其他類型一樣。與任何其他類型一樣，at 符號 (@) 用於表示句柄。下面的例子說明如何透過句柄將一個函數傳遞給另一個函數：

funcdef void my_function_type()；

void do_n_times(my_function_type@what，uint n)
{
對於（uint i = 0；i <n；i ++）
{
什麼（）;
}
}

無效列印訊息（）
{
alert("訊息", "答案是 42。");
}
空主（）
{
執行n次（列印訊息，3）；
}


這個原始程式碼的有趣部分是函數 do_n_times。它以函數句柄和數字作為參數，其效果是執行給定函數給定的次數。

當您編寫程式碼時，函數句柄特別有用，您知道程式碼將在某個時候執行某些外部函數，但在編寫時您無法知道將執行哪個函數。對於你的程式的每次運行來說它甚至可能不是相同的功能。在大多數情況下，您甚至會知道函數的簽名，只是不知道背後的實際實現。如果您遇到此類問題，請記住您的程式碼不需要硬連線來執行任何特定功能，而是可以使用程式碼使用者想要定義的任何功能的句柄。
13. 使用多個腳本
BGT 的一個非常有用的功能是能夠將多個腳本組合成一個程式。如果您正在編寫大型遊戲，這可能會很方便，因為查看腳本中的某個功能非常耗時。因此，我們可以對跨多個腳本的功能進行分類，例如用於選單功能的腳本、用於播放器的腳本、用於 AI 的腳本、用於聲音管理的腳本等。這樣，您的主腳本可能只有 200 行，而不是 1500 行。
要使用多個腳本，您必須使用以下語句將它們包含在主腳本中：

#包括“腳本名稱.bgt”

當發生這種情況時，引擎將讀取該腳本，就好像它是主腳本的一部分一樣，並且您使用的任何變量，函數或類別都可以存在於主腳本中或您的某個包含中。因此，沒有必要在包含的腳本中添加主函數，因為一旦編譯器從所有包含的腳本收集數據，主函數就會出現在主腳本中。因此，如果將主函數放在其他包含的腳本中，您將擁有多個 void main() 函數，並且編譯器將標記錯誤。
當您包含指定相對路徑的腳本時，它將首先在目前工作目錄中搜尋該腳本。這通常是儲存腳本的目錄。如果找不到該腳本，它將在 BGT 包含目錄中尋找。如果仍然找不到腳本，引擎將引發錯誤。
最終用戶不需要這些包含文件，因為一旦編譯完成，所有包含的腳本都將與程式一起打包。
最後說明
嗯，差不多就是這些了。如果你已經走到這一步，那麼你應該已經擁有了開始所需的一切。此時，我強烈建議您思考所學到的一切，然後回頭重新閱讀任何對您來說沒有意義的章節或部分。嘗試一下教程中提供的範例，以便熟悉它們，然後隨意地向世界展示您的遊戲。 BGT 幫助文件附帶了完整的函數和物件參考指南以及功能齊全的範例，如果您遇到困難可以隨時參考。
現在我能做的就是祝你好運，編碼愉快。希望很快能看到你的第一首熱門歌曲！
