GDScript 风格指南

This style guide lists conventions to write elegant GDScript. The goal is to encourage writing clean, readable code and promote consistency across projects, discussions, and tutorials. Hopefully, this will also support the development of auto-formatting tools.

Since GDScript is close to Python, this guide is inspired by Python’s PEP 8 programming style guide.

风格指南并不是硬性的规则手册。有时,您可能无法应用下面的一些准则。当这种情况发生时,使用你最好的判断,并询问其他开发人员的见解。

一般来说,在项目和团队中保持代码的一致性比遵循本指南进行tee更为重要。

注解

Godot的内置脚本编辑器默认使用了很多这些约定。让它帮助您。

Here is a complete class example based on these guidelines:

class_name StateMachine
extends Node
# Hierarchical State machine for the player.
# Initializes states and delegates engine callbacks
# (_physics_process, _unhandled_input) to the state.


signal state_changed(previous, new)

export var initial_state = NodePath()
var is_active = true setget set_is_active

onready var _state = get_node(initial_state) setget set_state
onready var _state_name = _state.name


func _init():
    add_to_group("state_machine")


func _ready():
    connect("state_changed", self, "_on_state_changed")
    _state.enter()


func _unhandled_input(event):
    _state.unhandled_input(event)


func _physics_process(delta):
    _state.physics_process(delta)


func transition_to(target_state_path, msg={}):
    if not has_node(target_state_path):
        return

    var target_state = get_node(target_state_path)
    assert target_state.is_composite == false

    _state.exit()
    self._state = target_state
    _state.enter(msg)
    Events.emit_signal("player_state_changed", _state.name)


func set_is_active(value):
    is_active = value
    set_physics_process(value)
    set_process_unhandled_input(value)
    set_block_signals(not value)


func set_state(value):
    _state = value
    _state_name = _state.name


func _on_state_changed(previous, new):
    print("state changed")
    emit_signal("state_changed")

格式

Encoding and special characters

  • Use line feed (LF) characters to break lines, not CRLF or CR. (editor default)
  • 在每个文件的末尾使用一个换行符。(编辑器默认设置)
  • Use UTF-8 encoding without a byte order mark. (editor default)
  • Use Tabs instead of spaces for indentation. (editor default)

缩进

每个缩进级别必须大于包含它的代码块。

良好的

for i in range(10):
    print("hello")

糟糕的

for i in range(10):
  print("hello")

for i in range(10):
        print("hello")

使用2个缩进级别来区分续行与常规代码块。

良好的

effect.interpolate_property(sprite, "transform/scale",
            sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
            Tween.TRANS_QUAD, Tween.EASE_OUT)

糟糕的

effect.interpolate_property(sprite, "transform/scale",
    sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
    Tween.TRANS_QUAD, Tween.EASE_OUT)

此规则的例外是数组、字典和枚举。使用单个缩进级别来区分连续行:

良好的

var party = [
    "Godot",
    "Godette",
    "Steve",
]

var character_dir = {
    "Name": "Bob",
    "Age": 27,
    "Job": "Mechanic",
}

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT,
}

糟糕的

var party = [
        "Godot",
        "Godette",
        "Steve",
]

var character_dir = {
        "Name": "Bob",
        "Age": 27,
        "Job": "Mechanic",
}

enum Tiles {
        TILE_BRICK,
        TILE_FLOOR,
        TILE_SPIKE,
        TILE_TELEPORT,
}

尾随逗号

在数组、字典和枚举的最后一行使用逗号。这将使版本控制中的重构更容易,差异也更大,因为添加新元素时不需要修改最后一行。

良好的

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT,
}

糟糕的

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT
}

单行列表中不需要尾随逗号,因此在这种情况下不要添加它们。

良好的

enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

糟糕的

enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,}

空白行

用两个空行包围函数和类定义:

func heal(amount):
    health += amount
    health = min(health, max_health)
    emit_signal("health_changed", health)


func take_damage(amount, effect=null):
    health -= amount
    health = max(0, health)
    emit_signal("health_changed", health)

函数内部使用一个空行来分隔逻辑部分。

Line length

把每行代码控制在100个字符以内。

如果可以的话,尽量把行控制在80个字符以下。这有助于在小屏幕上阅读代码,并在外部文本编辑器中并排打开两个脚本。例如,在查看差异修订时。

一条语句一行

Never combine multiple statements on a single line. No, C programmers, not even with a single line conditional statement.

良好的

if position.x > width:
    position.x = 0

if flag:
    print("flagged")

糟糕的

if position.x > width: position.x = 0

if flag: print("flagged")

该规则的唯一例外是三元运算符:

next_state = "fall" if not is_on_floor() else "idle"

避免不必要的圆括号

避免表达式和条件语句中的括号。除非对操作顺序有必要,否则它们只会降低可读性。

良好的

if is_colliding():
    queue_free()

糟糕的

if (is_colliding()):
    queue_free()

布尔运算

首选布尔运算符的纯英文版本,因为它们是最容易访问的:

  • Use and instead of &&.
  • Use or instead of ||.

也可以在布尔运算符周围使用括号来清除任何歧义。这可以使长表达式更容易阅读。

良好的

if (foo and bar) or baz:
    print("condition is true")

糟糕的

if foo && bar || baz:
    print("condition is true")

Comment spacing

Regular comments should start with a space, but not code that you comment out. This helps differentiate text comments from disabled code.

良好的

# This is a comment.
#print("This is disabled code")

糟糕的

#This is a comment.
# print("This is disabled code")

注解

在脚本编辑器中,要切换已注释的选定代码,请按<kbd>Ctrl</kbd><kbd>K</kbd>。此功能在选定行的开头添加一个#符号。

空格

Always use one space around operators and after commas. Also, avoid extra spaces in dictionary references and function calls.

良好的

position.x = 5
position.y = target_position.y + 10
dict["key"] = 5
my_array = [4, 5, 6]
print("foo")

糟糕的

position.x=5
position.y = mpos.y+10
dict ["key"] = 5
myarray = [4,5,6]
print ("foo")

不要使用空格垂直对齐表达式:

x        = 100
y        = 100
velocity = 500

引号

尽量使用双引号,除非单引号可以让字符串中需要转义的字符变少。见如下示例:

# Normal string.
print("hello world")

# Use double quotes as usual to avoid escapes.
print("hello 'world'")

# Use single quotes as an exception to the rule to avoid escapes.
print('hello "world"')

# Both quote styles would require 2 escapes; prefer double quotes if it's a tie.
print("'hello' \"world\"")

命名约定

These naming conventions follow the Godot Engine style. Breaking these will make your code clash with the built-in naming conventions, leading to inconsistent code.

类与节点

对类和节点名称使用PascalCase:

extends KinematicBody

Also use PascalCase when loading a class into a constant or a variable:

const Weapon = preload("res://weapon.gd")

函数与变量

Use snake_case to name functions and variables:

var particle_effect
func load_level():

Prepend a single underscore (_) to virtual methods functions the user must override, private functions, and private variables:

var _counter = 0
func _recalculate_path():

信号

Use the past tense to name signals:

signal door_opened
signal score_changed

常数和枚举

Write constants with CONSTANT_CASE, that is to say in all caps with an underscore (_) to separate words:

const MAX_SPEED = 200

对枚举*名称*使用PascalCase,对其成员使用CONSTANT_CASE ,因为它们是常量:

enum Element {
    EARTH,
    WATER,
    AIR,
    FIRE,
}

Code order

第一节主要讨论代码顺序。有关格式,请参见 格式。有关命名约定,请参见 命名约定

我们建议按以下方式组织GDScript代码:

01. tool
02. class_name
03. extends
04. # docstring

05. signals
06. enums
07. constants
08. exported variables
09. public variables
10. private variables
11. onready variables

12. optional built-in virtual _init method
13. built-in virtual _ready method
14. remaining built-in virtual methods
15. public methods
16. private methods

我们优化了顺序,使从上到下阅读代码变得容易,帮助第一次阅读代码的开发人员了解代码的工作原理,并避免与变量声明顺序相关的错误。

此代码顺序遵循四个经验法则:

  1. 首先是属性和信号,然后是方法。
  2. 公共变量优先于私有变量。
  3. 虚拟回调出现在类的接口之前。
  4. 对象的构造和初始化函数``_init``和``_ready``应该在运行时修改对象的函数之前。

类声明

如果代码要在编辑器中运行,请将 tool 关键字放在脚本的第一行。

如有必要,在后面加上 class_name。您可以使用此功能将GDScript文件转换为项目中的全局类型。有关更多信息,请参见 doc gdscript

然后,如果类扩展了内置类型,则添加 extends 关键字。

然后,您应该添加类的可选文档字符串作为注释。您可以使用它来向您的团队解释类的角色、工作原理,以及其他开发人员应该如何使用它,下面举个例子。

class_name MyNode
extends Node
# A brief description of the class's role and functionality.
# Longer description.

信号和属性

Write signal declarations, followed by properties, that is to say, member variables, after the docstring.

枚举应该在信号之后,因为您可以将它们用作其他属性的导出提示。

然后,按该顺序写入常量、导出变量、公共变量、私有变量和 onready 变量。

enum Jobs {KNIGHT, WIZARD, ROGUE, HEALER, SHAMAN}

const MAX_LIVES = 3

export(Jobs) var job = Jobs.KNIGHT
export var max_health = 50
export var attack = 5

var health = max_health setget set_health

var _speed = 300.0

onready var sword = get_node("Sword")
onready var gun = get_node("Gun")

注解

GDScript编译器在 _ready 函数回调之前计算onready变量。您可以使用它来缓存节点依赖项,也就是说,在您的类所依赖的场景中获取子节点。这就是上面的例子所展示的。

方法和静态函数

在类的属性之后是方法。

_init() 回调方法开始,引擎将在创建内存对象时调用该方法。接下来是 _ready() 回调,当Godot向场景树添加一个节点时调用它。

这些函数应首先出现,因为它们显示如何初始化对象。

其他内置的虚拟回调,如 _unhandling_input()_physics_process,应该放在后面。它们控制对象的主循环和与游戏引擎的交互。

类的其余接口,公共和私有方法,都是按照这个顺序出现的。

func _init():
    add_to_group("state_machine")


func _ready():
    connect("state_changed", self, "_on_state_changed")
    _state.enter()


func _unhandled_input(event):
    _state.unhandled_input(event)


func transition_to(target_state_path, msg={}):
    if not has_node(target_state_path):
        return

    var target_state = get_node(target_state_path)
    assert target_state.is_composite == false

    _state.exit()
    self._state = target_state
    _state.enter(msg)
    Events.emit_signal("player_state_changed", _state.name)


func _on_state_changed(previous, new):
    print("state changed")
    emit_signal("state_changed")

静态类型

从Godot 3.1开始,GDScript支持 可选的静态类型

类型提示

将冒号放在变量名称之后,不带空格,并在尽可能让GDScript编译器推断变量的类型。

良好的

onready var health_bar: ProgressBar = get_node("UI/LifeBar")

var health := 0 # The compiler will use the int type.

糟糕的

# The compiler can't infer the exact type and will use Node
# instead of ProgressBar.
onready var health_bar := get_node("UI/LifeBar")

当让编译器推断类型提示时,将冒号和等号一起写: :=

var health := 0 # The compiler will use the int type.

定义函数时,在返回类型箭头的两侧添加一个空格。

func heal(amount: int) -> void: