程式碼樣式方針

在參與貢獻 Godot 原始碼時需要遵守下述樣式方針。其中的一些規則會在 CI (持續整合, Continuos Integration) 過程中自動檢查,而審閱者也會要求你修正一些潛在的問題。因此,最好先依照下屬方式設定系統,並確保你的 Commit 都有符合本方針。

C++ 與 Objective-C

雖然沒有正式文件記載,Godot 的 C++ 與 Objective-C 程式碼樣式由開發者共識透過 clang-format 自動強制執行,這個美化工具會協助你遵守所有慣例。舉例如下:

  • 縮排與對齊均採 Tab 字元(分別為 1 個與 2 個 Tab)

  • 數學與賦值運算子前後,以及逗號之後須有一個空格

  • 指標與參照運算子應緊貼變數名稱,而非型別名稱

  • 關於標頭檔引用的細節請見下文

clang-format 採用的規則詳見 Godot 原始碼庫中的 .clang-format 檔案。

只要您確保您的風格符合周圍的程式碼,並且沒有引入行尾空白字元或以空白字元為基礎的縮排,您就沒問題了。然而,如果您打算定期貢獻,我們強烈建議您在本地端設定 clang-format,以檢查並自動修正您所有的提交。

警告

Godot 的程式碼風格*不*應套用於第三方程式碼,亦即包含在 Godot 原始碼樹中但並非專為我們的專案所編寫的程式碼。此類程式碼通常來自於具有其自身風格指南 (或沒有) 的不同上游專案,且不希望引入會使與上游儲存庫同步變得更加困難的差異。

第三方程式碼通常位於 thirdparty/ 資料夾,因此能輕鬆排除於格式化腳本之外。若少數情況下需將第三方程式碼片段直接寫入 Godot 檔案,可用 /* clang-format off *//* clang-format on */ 來讓 clang-format 忽略該區段。

也參考

本規範僅涵蓋程式碼格式化。關於拉取請求允許的語言功能,請參閱 C++ 使用規範

在本機使用 clang-format

您需要使用 clang-format 17 才能與 Godot 的格式相容。較新的版本可能適用,但較舊的版本可能不支援所有使用的選項,或以不同方式格式化某些內容,導致拉取請求中出現樣式問題。

Pre-commit 掛鉤

為方便使用,我們提供 Git 的掛鉤,搭配 pre-commit Python 框架,這會自動在您所有的提交上執行正確版本的 clang-format。設定方式如下:

pip install pre-commit
pre-commit install

你也可以手動執行 hook,指令為 pre-commit run

備註

先前我們在 misc/hooks 資料夾中提供了 hook。如果你是手動複製腳本,這些 hook 應該仍可運作,但符號連結會失效。如果你已經改用新系統,請執行 rm .git/hooks/*,移除不再需要的舊 hook。

安裝

clang-format 的安裝方式如下:

  • Linux:大部分發行版的 clang 工具鏈都會直接內建。若版本不符需求,可至 LLVM 網站 下載預編譯版本,或若你使用的是 Debian 衍生版,則可參考 上游套件庫

  • macOS 與 Windows:你可以從 LLVM 官方網站 下載預先編譯好的執行檔。你可能需要把執行檔所在的資料夾路徑新增到系統的 PATH 環境變數,才能直接執行 clang-format。

你可以用多種方式將 clang-format 套用至你的變更:

手動使用

你可以用以下指令手動將 clang-format 套用到一個或多個檔案:

clang-format -i <path/to/file(s)>
  • -i 代表改動應直接寫入到檔案內 (clang-format 預設只會將修正後的版本輸出到終端機上)。

  • 路徑可指向多個檔案,無論是逐一列出或用萬用字元(與一般 Unix shell 相同)。使用萬用字元時請避免誤用於 Godot 專案資料夾內的編譯物件檔(如 .o、.a)。建議用 core/*.{cpp,h} 而非 core/*

IDE 外掛

大多數的 IDE 或程式碼編輯器都有美化外掛,可以設定在每次儲存檔案時自動執行 clang-format。

此處僅列出部分用於一些 IDE 的美化外掛:

(歡迎提出 Pull Request,協助擴充此清單以納入更多經過驗證的外掛。)

標頭檔引用

新增 C++ 或 Objective-C 檔案,或對既有檔案引用新標頭時,請遵守以下規範:

  • 檔案開頭應放置 Godot 版權標頭與 MIT 授權條款,可從其他檔案複製,並確保檔名正確。

  • .h 標頭檔內,應以 FILENAME_H 形式加入 include guard(防止重複引用)。

  • .cpp 檔案(如 filename.cpp)中,第一個 include 應為該類別宣告檔(如 #include "filename.h"),並於其後加一空行分隔。

  • 接著是 Godot 自身程式碼的標頭檔,須以字母順序(clang-format 會自動排序)並使用相對於根目錄的路徑引用,且以引號包覆(如 #include "core/object.h")。Godot 標頭檔引用區塊結束後應加一空行分隔。

  • 最後是第三方標頭檔(來自 thirdparty 或系統 include 路徑),應以 < > 包覆(如 #include <png.h>)。第三方標頭區段結束後同樣需加一空行分隔。

  • Godot 與第三方標頭檔應於實際需要的檔案引用,若於宣告式程式碼中使用請於 .h 檔 include,僅於實作(命令式)程式碼中使用則於 .cpp include。

範例:

my_new_file.h
/**************************************************************************/
/*  my_new_file.h                                                         */
/**************************************************************************/
/*                         This file is part of:                          */
/*                             GODOT ENGINE                               */
/*                        https://godotengine.org                         */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
/*                                                                        */
/* Permission is hereby granted, free of charge, to any person obtaining  */
/* a copy of this software and associated documentation files (the        */
/* "Software"), to deal in the Software without restriction, including    */
/* without limitation the rights to use, copy, modify, merge, publish,    */
/* distribute, sublicense, and/or sell copies of the Software, and to     */
/* permit persons to whom the Software is furnished to do so, subject to  */
/* the following conditions:                                              */
/*                                                                        */
/* The above copyright notice and this permission notice shall be         */
/* included in all copies or substantial portions of the Software.        */
/*                                                                        */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
/**************************************************************************/

#ifndef MY_NEW_FILE_H
#define MY_NEW_FILE_H

#include "core/hash_map.h"
#include "core/list.h"
#include "scene/gui/control.h"

#include <png.h>

...

#endif // MY_NEW_FILE_H
my_new_file.cpp
/**************************************************************************/
/*  my_new_file.cpp                                                       */
/**************************************************************************/
/*                         This file is part of:                          */
/*                             GODOT ENGINE                               */
/*                        https://godotengine.org                         */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
/*                                                                        */
/* Permission is hereby granted, free of charge, to any person obtaining  */
/* a copy of this software and associated documentation files (the        */
/* "Software"), to deal in the Software without restriction, including    */
/* without limitation the rights to use, copy, modify, merge, publish,    */
/* distribute, sublicense, and/or sell copies of the Software, and to     */
/* permit persons to whom the Software is furnished to do so, subject to  */
/* the following conditions:                                              */
/*                                                                        */
/* The above copyright notice and this permission notice shall be         */
/* included in all copies or substantial portions of the Software.        */
/*                                                                        */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
/**************************************************************************/

#include "my_new_file.h"

#include "core/math/math_funcs.h"
#include "scene/gui/line_edit.h"

#include <zlib.h>
#include <zstd.h>

Java

Godot 的 Java 程式碼(多數位於 platform/android)也由 clang-format 強制格式化,請參考上方設定說明。請注意,本樣式指南僅適用於 Godot 開發與維護的程式碼,第三方程式碼(如 java/src/com/google 目錄)不在此限。

Python

Godot 的 SCons 建置系統以 Python 寫成,原始碼樹中也有多個 Python 腳本。

針對這些,我們使用 Ruff linter 與程式碼格式化工具

在本機使用 ruff

首先,你需要安裝 Ruff。執行 Ruff 需要 Python 3.7 以上版本。

安裝

安裝 ruff 的方式如下:

pip3 install ruff --user

你可以用不同方式將 ruff 套用到你的變更上:

手動使用

你可以用下列指令手動將 ruff 套用到一個或多個檔案:

ruff -l 120 <path/to/file(s)>
  • -l 120 表示每行最多允許 120 個字元。這個數字是由多位開發人員公認的。

  • 路徑可以指向多個檔案,可以直接寫出多個檔案,或是使用如一般 Unix Shell 的萬用字元。

Pre-commit 掛鉤

為了方便使用,我們提供了 Git 的 pre-commit Python 框架 hook,可以在每次提交時自動以正確版本執行 ruff。設定方式如下:

pip install pre-commit
pre-commit install

你也可以手動執行 hook,指令為 pre-commit run

備註

先前我們在 misc/hooks 資料夾中提供了 hook。如果你是手動複製腳本,這些 hook 應該仍可運作,但符號連結會失效。如果你已經改用新系統,請執行 rm .git/hooks/*,移除不再需要的舊 hook。

編輯器整合

許多 IDE 或程式碼編輯器都有美化外掛,可以設定在每次儲存檔案時自動執行 ruff。詳細資訊請參考 Ruff Integrations

C# 風格指南

本注釋風格指南適用於Godot程式碼庫中使用的所有程式設計語言.

  • 以空間字元開始注釋, 以將其與禁用程式碼區分開來.

  • 注釋使用句子大小寫. 注釋以大寫字母開頭, 並始終以句號結束.

  • 使用反引號引用變數和函式名和值.

  • 將注釋控制在~100個字元。

  • 你可以在需要時使用 TODO:FIXME:NOTE:WARNING:HACK: 作為提示標註。

範例:

// Compute the first 10,000 decimals of Pi.
// FIXME: Don't crash when computing the 1,337th decimal due to `increment`
//        being negative.

不要在註解中重複程式碼的內容,應解釋*為什麼*這麼做,而不是*怎麼做*。

錯誤範例:

// Draw loading screen.
draw_load_screen();

你可以在函式或巨集定義上面使用Javadoc風格的注釋. 建議只對不公開給腳本的方法使用Javadoc風格的注釋. 這是因為公開的方法應該在 class reference XML 中進行記錄.

範例:

/**
 * Returns the number of nodes in the universe.
 * This can potentially be a very large number, hence the 64-bit return type.
 */
uint64_t Universe::get_node_count() {
    // ...
}

對於成員變數,請不要使用 Javadoc 風格註解,改用單行註解:

class Universe {
    // The cached number of nodes in the universe.
    // This value may not always be up-to-date with the current number of nodes
    // in the universe.
    uint64_t node_count_cached = 0;
};