Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Посібник зі стилю 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 без маркера порядку байтів. (за замовчування редактора)

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

Відступ

Кожен рівень відступу повинен бути на один відступ більшим, ніж блок, що містить його.

Правильно:

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,
}

Заключна кома

Використовуйте заключну кому в останньому рядку в масивах, словниках та перерахунках. Це спрощує рефакторинг та покращення в пізніших версіях, оскільки останній рядок не потрібно змінювати при додаванні нових елементів.

Правильно:

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

Пробіл коментаря

Звичайні коментарі (#) і коментарі до документації (##) мають починатися з пробілу, але не з коду, який ви коментуєте. Крім того, коментарі регіону коду (#region/#endregion) мають відповідати цьому точному синтаксису, тому вони не повинні починатися з пробілу.

Використання місця для звичайних коментарів і коментарів до документації допомагає відрізнити текстові коментарі від вимкненого коду.

Правильно:

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

Неправильно:

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

Примітка

У редакторі сценаріїв, щоб увімкнути коментування вибраного коду, натисніть Ctrl + K. Ця функція додає/видаляє один знак # перед будь-яким кодом у вибраних рядках.

Вільний простір

Завжди використовуйте один пробіл навколо операторів і після ком. Також уникайте зайвих пробілів у словникових посиланнях і викликах функцій. Єдиний виняток для однорядкових словникових декларацій, де слід додати пробіл після відкриваючої дужки та перед закриваючою дужкою. Завдяки цьому словник легше візуально відрізнити від масиву, оскільки символи [] виглядають схожими на {} у більшості шрифтів.

Правильно:

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

Конвенції іменування

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. As a summary table:

Тип

Convention

Приклад

Назви файлів

snake_case

yaml_parser.gd

Class names

PascalCase

class_name YAMLParser

Node names

PascalCase

Camera3D, Player

Функції

snake_case

func load_level():

Змінні

snake_case

var particle_effect

Сигнали

snake_case

signal door_opened

Константи

CONSTANT_CASE

const MAX_SPEED = 200

Enum names

PascalCase

enum Element

Enum members

CONSTANT_CASE

{EARTH, WATER, AIR, FIRE}

Назви файлів

Використовуйте 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,
}

Write enums with each item on its own line. This allows adding documentation comments above each item more easily, and also makes for cleaner diffs in version control when items are added or removed.

Правильно:

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

Неправильно:

enum Element { EARTH, WATER, AIR, FIRE }

Порядок коду

This section focuses on code order. For formatting, see Форматування. For naming conventions, see Конвенції іменування.

Ми пропонуємо організувати код 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. subclasses

And put the class methods and variables in the following order depending on their access modifiers:

1. public
2. private

Ми оптимізували порядок, щоб полегшити читання коду зверху вниз, щоб допомогти розробникам, які вперше читають код, зрозуміти, як він працює, та уникнути помилок, пов'язаних із порядком оголошення змінних.

Цей порядок коду відповідає чотирьом правилам:

  1. Першими стають властивості та сигнали, а потім - методи.

  2. Загальнодоступні йдуть перед приватними.

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

  4. Конструктори об'єктів та ініціалізації функцій, _init і _ready, перед функціями, що змінюють об'єкт під час виконання.

Оголошення класу

Якщо код призначений для запуску в редакторі, розмістіть анотацію @tool у першому рядку сценарію.

Follow with the optional @icon then the class_name if necessary. You can turn a GDScript file into a global type in your project using class_name. For more information, see Посилання на GDScript.

Потім додайте ключове слово extends, якщо клас розширює вбудований тип.

Після цього ви повинні мати необов’язкові коментарі документації класу. Ви можете використовувати це, щоб пояснити роль вашого класу своїм товаришам по команді, як він працює та як інші розробники повинні його використовувати, наприклад.

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.

Сигнали та властивості

Записуйте оголошення сигналу, далі властивості, тобто змінні-члени.

Перерахунки повинні йти за сигналами, оскільки ви можете використовувати їх як підказки щодо експорту інших властивостей.

Потім записуйте константи, експортовані змінні, загальнодоступні, приватні та onready змінні, в такому порядку.

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 обчислює змінні @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 supports optional static typing.

Заявлені типи

Щоб оголосити тип змінної, використовуйте <variable>: <type>:

var health: int = 0

Визначте тип повернення функції використовуючи -> <type>:

func heal(amount: int) -> void:

Визначені типи

У більшості випадків ви можете дозволити компілятору визначити тип, використовуючи :=. Віддавайте перевагу :=, коли тип записаний у тому ж рядку, що й призначення, інакше віддавайте перевагу явному написанню типу.

Правильно:

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.

Включіть підказку типу, якщо тип неоднозначний, і пропустіть підказку типу, якщо вона зайва.

Неправильно:

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()

У деяких випадках тип потрібно вказати явно, інакше поведінка буде не такою, як очікувалося, оскільки компілятор зможе використовувати лише тип повернення функції. Наприклад, get_node() не може визначити тип, якщо сцена або файл вузла не завантажено в пам’ять. У цьому випадку вам слід явно вказати тип.

Правильно:

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