Руководство по стилю GDScript
В этом руководстве по стилю перечислены соглашения для написания элегантного кода на GDScript. Цель заключается в том, чтобы вдохновить писать чистый, читаемый код и способствовать согласованности между проектами, обсуждениями и учебными материалами. Надеемся, это также будет способствовать разработке инструментов автоматического форматирования.
Поскольку GDScript очень близок к Python, это руководство вдохновлено руководством по стилю программирования на Python PEP 8.
Руководства по стилю не являются жестким сводом правил. Иногда у вас не будет возможности применить ниже приведенные рекомендации. Когда это случится, поступайте по своему усмотрению и попросите коллег-разработчиков поделиться своими соображениями.
В целом, поддержание согласованности кода в ваших проектах и в команде важнее, чем следование этому руководству.
Примечание
Встроенный в 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. (редактор по умолчанию)
Используйте символ перевода строки в конце каждого файла. (редактор по умолчанию)
Используйте кодировку 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 Tile {
BRICK,
FLOOR,
SPIKE,
TELEPORT,
}
Плохо:
var party = [
"Godot",
"Godette",
"Steve",
]
var character_dict = {
"Name": "Bob",
"Age": 27,
"Job": "Mechanic",
}
enum Tile {
BRICK,
FLOOR,
SPIKE,
TELEPORT,
}
Висящая запятая (Последняя запятая)
Используйте висящую запятую на последней строке в массивах, словарях, и перечислениях. Такой подход позволяет упростить рефакторинг и улучшить сравнение в контроле версий, так как последняя строка не нуждается в изменении, когда добавляются новые элементы.
Хорошо:
var array = [
1,
2,
3,
]
Плохо:
var array = [
1,
2,
3
]
Висящие запятые не нужны в однострочных списках, поэтому в этом случае не добавляйте их.
Хорошо:
var array = [1, 2, 3]
Плохо:
var array = [1, 2, 3,]
Пустые строки
Окружите функции и определения классов двумя пустыми строками:
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)
Используйте одну пустую строку внутри функций для отделения логических частей друг от друга.
Примечание
В справочнике классов и в коротких фрагментах кода в этой документации мы используем одну строку между классами и определениями функций.
Длина строки
Придерживайтесь длине строк кода не более 100 символов.
Если можете, постарайтесь придерживаться длине строк в 80 символов. Это поможет читать код на небольших дисплеях и с двумя скриптами открытыми бок о бок в стороннем текстовом редакторе. Например, чтобы посмотреть на различия.
Одно выражение на строку
Избегайте объединения нескольких операторов в одной строке, включая условные операторы, чтобы соответствовать правилам стиля GDScript для удобства чтения.
Хорошо:
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вместо||.Используйте
notвместо!.
Вы также можете использовать круглые скобки вокруг логических операторов во избежание неоднозначностей. Это может облегчить чтение длинных выражений.
Хорошо:
if (foo and bar) or not baz:
print("condition is true")
Плохо:
if foo && bar || !baz:
print("condition is true")
Пробел
Всегда используйте один пробел вокруг операторов и после запятых. Также избегайте лишних пробелов в ссылках на словари и вызовах функций. Исключением являются однострочные объявления словарей, где пробел следует добавлять после открывающей фигурной скобки и перед закрывающей. Это позволяет визуально отличить словарь от массива, поскольку символы [] в большинстве шрифтов выглядят почти как {}.
Хорошо:
position.x = 5
position.y = target_position.y + 10
dict["key"] = 5
my_array = [4, 5, 6]
my_dictionary = { key = "value" }
print("foo")
Плохо:
position.x=5
position.y = mpos.y+10
dict ["key"] = 5
myarray = [4,5,6]
my_dictionary = {key = "value"}
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 |
|
Имена узлов |
PascalCase |
|
Функции |
snake_case |
|
Переменные |
snake_case |
|
Сигналы |
snake_case |
|
Константы |
CONSTANT_CASE |
|
Имена перечислений (Enum) |
PascalCase |
|
Члены перечисления ((Enum)) |
CONSTANT_CASE |
|
Имена файлов
Используйте формат 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,
}
Пишите перечисления, каждый элемент которых находится на отдельной строке. Это упрощает добавление комментариев к документации над каждым элементом, а также способствует более четкому отображению различий в системе контроля версий при добавлении или удалении элементов.
Хорошо:
enum Element {
EARTH,
WATER,
AIR,
FIRE,
}
Плохо:
enum Element { EARTH, WATER, AIR, FIRE }
Порядок кода
В этом разделе рассматривается порядок кодирования. О форматировании см. Форматирование. О соглашениях об именовании см. Соглашения об именовании.
Мы предлагаем организовывать код на GDScript таким образом:
01. @tool, @icon, @static_unload
02. class_name
03. extends
04. ## doc comment
05. signals
06. enums
07. constants
08. static variables
09. @export variables
10. remaining regular variables
11. @onready variables
12. _static_init()
13. remaining static methods
14. overridden built-in virtual methods:
1. _init()
2. _enter_tree()
3. _ready()
4. _process()
5. _physics_process()
6. remaining virtual methods
15. overridden custom methods
16. remaining methods
17. inner classes
И расположите методы и переменные класса в следующем порядке в зависимости от их модификаторов доступа:
1. public
2. private
Мы оптимизировали порядок, чтобы упростить чтение кода сверху вниз и помочь разработчикам, которые читают этот код впервые, понять, как он работает и избежать ошибок, связанных с порядком объявления переменных.
Этот порядок кода следует четырем правилам большого пальца (эмпирическая закономерность):
Свойства и сигналы идут первыми, сопровождаемые методами.
Модификатор public идет перед модификатором private.
Виртуальные функции обратного вызова идут перед интерфейсом класса.
Создание объектов и инициализация функций,
_initи_ready, идут перед функциями, которые изменяют объект во время выполнения.
Объявление класса
Если код предназначен для запуска в редакторе, поместите аннотацию @tool в первую строку скрипта.
После этого добавьте необязательный @icon, а затем, при необходимости, class_name. Вы можете преобразовать файл GDScript в глобальный тип в своём проекте с помощью class_name. Подробнее см. Регистрация именованных классов. Если класс должен быть abstract class, добавьте @abstract перед ключевым словом class_name.
Затем добавьте ключевое слово extends, если класс расширяет встроенный тип.
После этого у вас должны быть необязательные комментарии к классу documentation comments. Вы можете использовать их, например, чтобы объяснить коллегам по команде роль класса, как он работает и как его следует использовать другим разработчикам.
@abstract
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.
Для внутренних классов используйте однострочные объявления:
## A brief description of the class's role and functionality.
##
## The description of the script, what it can do,
## and any further detail.
@abstract class MyNode extends Node:
pass
Сигналы и свойства
Пишите объявления сигналов, затем свойства, иными словами, переменные-члены после строки документации.
Перечисления должны идти после сигналов, так как вы можете использовать их в качестве подсказок экспорта для других свойств.
Затем, пишите константы, экспортированные переменные, модификаторы public, private, и инициализированные переменные, в таком порядке.
signal player_spawned(position)
enum Job {
KNIGHT,
WIZARD,
ROGUE,
HEALER,
SHAMAN,
}
const MAX_LIVES = 3
@export var job: Job = Job.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 вычисляет переменные @onready непосредственно перед обратным вызовом _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()
Статическая типизация
GDScript поддерживает :optional static typing.
Объявленные типы
Для объявление типа переменной, используйте конструкцию <переменная>: <тип>:
var health: int = 0
Для объявления возвращаемого типа функции, используйте конструкцию -> <тип>:
func heal(amount: int) -> void:
Определяемые типы
В большинстве случаев можно позволить компилятору определить тип с помощью :=. Если тип указан на той же строке, что и присваивание, предпочтительнее использовать :=, в противном случае предпочтительнее указывать тип явно.
Хорошо:
# The type can be int or float, and thus should be stated explicitly.
var health: int = 0
# The type is clearly inferred as Vector3.
var direction := Vector3(1, 2, 3)
Включите подсказку о типе, если тип неоднозначен, и опустите подсказку о типе, если она избыточна.
Плохо:
# Typed as int, but it could be that float was intended.
var health := 0
# The type hint has redundant information.
var direction: Vector3 = Vector3(1, 2, 3)
# What type is this? It's not immediately clear to the reader, so it's bad.
var value := complex_function()
В некоторых случаях тип необходимо указывать явно, иначе поведение будет не таким, как ожидалось, поскольку компилятор сможет использовать только возвращаемый тип функции. Например, get_node() не может определить тип, если сцена или файл узла не загружены в память. В этом случае тип следует указать явно.
Хорошо:
@onready var health_bar: ProgressBar = get_node("UI/LifeBar")
Плохо:
# The compiler can't infer the exact type and will use Node
# instead of ProgressBar.
@onready var health_bar := get_node("UI/LifeBar")
В качестве альтернативы вы можете использовать ключевое слово as для приведения типа возвращаемого значения, и этот тип будет использоваться для определения типа переменной.
@onready var health_bar := get_node("UI/LifeBar") as ProgressBar
# health_bar will be typed as ProgressBar
Примечание
Этот параметр считается более type-safe, чем подсказки типов, но также и менее безопасным по отношению к null, поскольку он молча приводит переменную к null в случае несоответствия типов во время выполнения, без ошибки/предупреждения.
Интервал в комментариях
Обычные комментарии (
#) и комментарии документации (##) должны начинаться с пробела, но не с кода, который вы комментируете. Кроме того, комментарии к областям кода (#region/#endregion) должны следовать этому точному синтаксису, поэтому они не должны начинаться с пробела.Использование пространства для обычных и документационных комментариев помогает отличить текстовые комментарии от отключенного кода.
Хорошо:
Плохо:
Примечание
В редакторе скриптов, чтобы включить или отключить комментирование выделенного кода, нажмите Ctrl + K. Эта функция добавляет/удаляет одиночный символ
#перед любым кодом в выделенных строках.Предпочитайте писать комментарии на отдельной строке, а не на встроенных комментариях (комментариях, которые пишутся на той же строке, что и код). Встроенные комментарии лучше всего использовать для коротких комментариев, обычно состоящих максимум из нескольких слов:
Хорошо:
Плохо: