Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Руководство по стилю GDScript

В этом руководстве по стилю перечислены соглашения для написания элегантного кода на GDScript. Цель заключается в том, чтобы вдохновить писать чистый, читаемый код и способствовать согласованности между проектами, обсуждениями и учебными материалами. Надеемся, это также будет способствовать разработке инструментов автоматического форматирования.

Поскольку GDScript очень близок к Python, это руководство вдохновлено руководством по стилю программирования на Python`PEP 8 <https://www.python.org/dev/peps/pep-0008/>.

Руководства по стилю не являются жестким сводом правил. Иногда у вас не будет возможности применить ниже приведенные рекомендации. Когда это случится, поступайте по своему усмотрению и попросите коллег-разработчиков поделиться своими соображениями.

В целом, поддержание согласованности кода в ваших проектах и в команде важнее, чем следование этому руководству.

Примечание

Встроенный в Godot редактор скриптов использует большинство этих соглашений по умолчанию. Позвольте ему помочь вам.

Вот полный пример класса основанный на этих рекомендациях:

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


signal state_changed(previous, new)

@export var initial_state: Node
var is_active = true:
    set = set_is_active

@onready var _state = initial_state:
    set = set_state
@onready var _state_name = _state.name


func _init():
    add_to_group("state_machine")


func _enter_tree():
    print("this happens before the ready method!")


func _ready():
    state_changed.connect(_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.player_state_changed.emit(_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")
    state_changed.emit()


class State:
    var foo = 0

    func _init():
        print("Hello!")

Форматирование

Кодирование и специальные символы

  • Используйте символы подачи строк (LF) для разрыва строк, а не CRLF или CR. (редактор по умолчанию)

  • Используйте символ перевода строки в конце каждого файла. (editor default)

  • Используйте кодировку UTF-8 без маркера последовательности байтов. (редактор по умолчанию)

  • Используйте табы вместо пробелов для отступов. (редактор по умолчанию)

Отступ

Каждый уровень отступов должен быть на один больше чем у блока, в котором он содержится.

Хорошо:

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_dict = {
    "Name": "Bob",
    "Age": 27,
    "Job": "Mechanic",
}

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

Плохо:

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

var character_dict = {
        "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)
    health_changed.emit(health)


func take_damage(amount, effect=null):
    health -= amount
    health = max(0, health)
    health_changed.emit(health)

Используйте одну пустую строку внутри функций для отделения логических частей друг от друга.

Примечание

We use a single line between classes and function definitions in the class reference and in short code snippets in this documentation.

Длина строки

Придерживайтесь длине строк кода не более 100 символов.

Если можете, постарайтесь придерживаться длине строк в 80 символов. Это поможет читать код на небольших дисплеях и с двумя скриптами открытыми бок о бок в стороннем текстовом редакторе. Например, чтобы посмотреть на различия.

Одно выражение на строку

Никогда не объединяйте несколько выражений в одну строку. Нет, программисты C, не надо писать условные выражения в одну строку (за исключением тернарного оператора).

Хорошо:

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

if flag:
    print("flagged")

Плохо:

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

if flag: print("flagged")

Единственным исключением из этого правила является тернарный оператор:

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

Форматирование многострочных операторов для удобства чтения

Когда у вас особенно длинные операторы if или вложенные тернарные выражения, их перенос на несколько строк улучшает читаемость. Поскольку строки продолжения по-прежнему являются частью одного и того же выражения, следует использовать 2 уровня отступа вместо одного.

GDScript позволяет заключать инструкции в несколько строк, используя круглые скобки или обратную косую черту. В этом руководстве по стилю рекомендуется использовать круглые скобки, поскольку они упрощают рефакторинг. Используя обратную косую черту, вы должны убедиться, что последняя строка никогда не содержит обратной косой черты в конце. С круглыми скобками вам не нужно беспокоиться о том, что последняя строка будет иметь обратную косую черту в конце.

При переносе условного выражения на несколько строк ключевые слова and/or следует размещать в начале продолжения строки, а не в конце предыдущей строки.

Хорошо:

var angle_degrees = 135
var quadrant = (
        "northeast" if angle_degrees <= 90
        else "southeast" if angle_degrees <= 180
        else "southwest" if angle_degrees <= 270
        else "northwest"
)

var position = Vector2(250, 350)
if (
        position.x > 200 and position.x < 400
        and position.y > 300 and position.y < 400
):
    pass

Плохо:

var angle_degrees = 135
var quadrant = "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest"

var position = Vector2(250, 350)
if position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400:
    pass

Избегайте лишних скобок

Избегайте скобок в выражениях и условных операторах. Если это не требуется для порядка операций или переноса на несколько строк, они только ухудшают читаемость.

Хорошо:

if is_colliding():
    queue_free()

Плохо:

if (is_colliding()):
    queue_free()

Логические операторы

Отдавайте предпочтение простым английским версиям логических операторов, так как они наиболее доступны:

  • Используйте `` and`` вместо `` && ``.

  • Используйте or вместо ||.

  • Use not instead of !.

Вы также можете использовать круглые скобки вокруг логических операторов во избежание неоднозначностей. Это может облегчить чтение длинных выражений.

Хорошо:

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

Плохо:

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

Интервал в комментариях

Regular comments (#) and documentation comments (##) should start with a space, but not code that you comment out. Additionally, code region comments (#region/#endregion) must follow that precise syntax, so they should not start with a space.

Using a space for regular and documentation comments 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")

Примечание

In the script editor, to toggle commenting of the selected code, press Ctrl + K. This feature adds/removes a single # sign before any code on the selected lines.

Пробел

Всегда используйте один пробел до и после операторов, а также после запятых. Кроме того, избегайте лишних пробелов в ссылках на словарь и вызовах функций.

Хорошо:

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\"")

Номера

Не пропускайте нули в числах с плавающей точкой. Это делает их менее читаемыми, и более сложно отличимыми от обычных чисел.

Хорошо:

var float_number = 0.234
var other_float_number = 13.0

Плохо:

var float_number = .234
var other_float_number = 13.

Используйте нижний регистр в записи шестнадцатеричных чисел, так их становится проще прочесть.

Хорошо:

var hex_number = 0xfb8c0b

Плохо:

var hex_number = 0xFB8C0B

Используйте преимущество использования нижнего подчеркивания в переменных GDScript чтобы сделать большие числа более читаемыми.

Хорошо:

var large_number = 1_234_567_890
var large_hex_number = 0xffff_f8f8_0000
var large_bin_number = 0b1101_0010_1010
# Numbers lower than 1000000 generally don't need separators.
var small_number = 12345

Плохо:

var large_number = 1234567890
var large_hex_number = 0xfffff8f80000
var large_bin_number = 0b110100101010
# Numbers lower than 1000000 generally don't need separators.
var small_number = 12_345

Соглашения об именовании

Эти соглашения об именовании следуют стилю Godot Engine. Нарушение этих правил приведет к конфликту вашего кода со встроенными соглашениями об именовании, что приведет к противоречивому коду.

Имена файлов

Используйте snake_case (слова из маленьких букв с подчеркиваниями) для имен файлов. Для именованных классов преобразуйте имя класса PascalCase (слова с заглавными буквами) в snake_case:

# This file should be saved as `weapon.gd`.
class_name Weapon
extends Node
# This file should be saved as `yaml_parser.gd`.
class_name YAMLParser
extends Object

Это согласуется с тем, как файлы C++ называются в исходном коде Godot. Это также позволяет избежать проблем с чувствительностью к регистру, которые могут возникнуть при экспорте проекта из Windows на другие платформы.

Классы и узлы

Используйте PascalCase для имен классов и узлов:

extends CharacterBody3D

Также используйте PascalCase (слова с заглавными буквами) при загрузке класса в константу или переменную:

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

Функции и переменные

Используйте snake_case для именования функций и переменных:

var particle_effect
func load_level():

Добавьте одно нижнее подчеркивание (_) к наименованию виртуальных методов (функции, которые пользователь должен переопределить), приватных функций и приватных переменных:

var _counter = 0
func _recalculate_path():

Сигналы

Используйте прошедшее время в именах сигналов:

signal door_opened
signal score_changed

Константы и перечисления

Пишите константы с CONSTANT_CASE, все большими буквами, с нижним подчеркиванием (_) в качестве разделителя слов:

const MAX_SPEED = 200

Используйте PascalCase для имен перечислений и CONSTANT_CASE для их значений, поскольку они являются константами:

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

Порядок кода

Это первый раздел сфокусированный на порядке кода. Для форматирования смотрите Форматирование. Для соглашений по именованию смотрите Соглашения об именовании.

Мы предлагаем организовывать код на GDScript таким образом:

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

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

12. optional built-in virtual _init method
13. optional built-in virtual _enter_tree() method
14. built-in virtual _ready method
15. remaining built-in virtual methods
16. public methods
17. private methods
18. subclasses

Мы оптимизировали порядок, чтобы упростить чтение кода сверху вниз и помочь разработчикам, которые читают этот код впервые, понять, как он работает и избежать ошибок, связанных с порядком объявления переменных.

Этот порядок кода следует четырем правилам большого пальца (эмпирическая закономерность):

  1. Свойства и сигналы идут первыми, сопровождаемые методами.

  2. Модификатор public идет перед модификатором private.

  3. Виртуальные функции обратного вызова идут перед интерфейсом класса.

  4. Создание объектов и инициализация функций, _init и _ready, идут перед функциями, которые изменяют объект во время выполнения.

Объявление класса

If the code is meant to run in the editor, place the @tool annotation on the first line of the script.

Follow with the class_name if necessary. You can turn a GDScript file into a global type in your project using this feature. For more information, see Справочник по GDScript.

Then, add the extends keyword if the class extends a built-in type.

Following that, you should have the class's optional documentation comments. You can use that to explain the role of your class to your teammates, how it works, and how other developers should use it, for example.

class_name MyNode
extends Node
## A brief description of the class's role and functionality.
##
## The description of the script, what it can do,
## and any further detail.

Сигналы и свойства

Пишите объявления сигналов, затем свойства, иными словами, переменные-члены после строки документации.

Перечисления должны идти после сигналов, так как вы можете использовать их в качестве подсказок экспорта для других свойств.

Затем, пишите константы, экспортированные переменные, модификаторы public, private, и инициализированные переменные, в таком порядке.

signal player_spawned(position)

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

const MAX_LIVES = 3

@export var job: Jobs = Jobs.KNIGHT
@export var max_health = 50
@export var attack = 5

var health = max_health:
    set(new_health):
        health = new_health

var _speed = 300.0

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

Примечание

Компилятор GDScript вычисляет инициализированные переменные прямо перед обратным вызовом``_ready``. Вы можете использовать это для кэширования зависимости узлов, иными словами, получить дочерние узлы в сцене, на которую опирается ваш класс. Пример выше показывает это.

Переменные-члены

Не объявляйте переменные-члены, если они используются только локально в методе, так как это затрудняет выполнение кода. Вместо этого объявите их как локальные переменные в теле метода.

Локальные переменные

Объявляйте локальные переменные как можно ближе к их первому использованию. Это облегчает отслеживание кода, не требуя слишком большой прокрутки, чтобы найти, где была объявлена переменная.

Методы и статические функции

После свойств класса идут методы.

Начните с метода обратного вызова _init(), который будет вызван движком при создании объекта в памяти. Затем следует обратный вызов `` _ready () ``, который Godot вызывает при добавлении узла в дерево сцены.

Эти функции должны следовать первыми, так как они показывают, как инициализируется объект.

Другие встроенные виртуальные обратные вызовы, такие как _unhandled_input() и _physics_process, должны следовать дальше. Они контролируют основной цикл объекта и взаимодействие с игровым движком.

Остальной интерфейс класса, публичные и приватные методы — следуют после, в указанном порядке.

func _init():
    add_to_group("state_machine")


func _ready():
    state_changed.connect(_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.player_state_changed.emit(_state.name)


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

Статическая типизация

Начиная с версии Godot 3.1, GDScript поддерживает optional static typing.

Объявленные типы

Для объявление типа переменной, используйте конструкцию <переменная>: <тип>:

var health: int = 0

Для объявления возвращаемого типа функции, используйте конструкцию -> <тип>:

func heal(amount: int) -> void:

Определяемые типы

In most cases you can let the compiler infer the type, using :=. Prefer := when the type is written on the same line as the assignment, otherwise prefer writing the type explicitly.

Хорошо:

var health: int = 0 # The type can be int or float, and thus should be stated explicitly.
var direction := Vector3(1, 2, 3) # The type is clearly inferred as Vector3.

Include the type hint when the type is ambiguous, and omit the type hint when it's redundant.

Плохо:

var health := 0 # Typed as int, but it could be that float was intended.
var direction: Vector3 = Vector3(1, 2, 3) # The type hint has redundant information.

# What type is this? It's not immediately clear to the reader, so it's bad.
var value := complex_function()

In some cases, the type must be stated explicitly, otherwise the behavior will not be as expected because the compiler will only be able to use the function's return type. For example, get_node() cannot infer a type unless the scene or file of the node is loaded in memory. In this case, you should set the type explicitly.

Хорошо:

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

В качестве альтернативы вы можете использовать ключевое слово as для приведения типа возвращаемого значения, и этот тип будет использоваться для определения типа переменной.

@onready var health_bar := get_node("UI/LifeBar") as ProgressBar
# health_bar will be typed as ProgressBar

Этот вариант также считается больше type-safe, чем первый.

Плохо:

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