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.

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

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

Примітка

Вбудований в 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,
}

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

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

Правильно:

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 символів. Це допомагає читати код на невеликих дисплеях та з двома скриптами, відкритими поруч у зовнішньому текстовому редакторі. Наприклад, щоб переглянути на наявність різниці.

Одне твердження на рядок

Ніколи не комбінуйте кілька тверджень в одному рядку.

Правильно:

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"

Format multiline statements for readability

When you have particularly long if statements or nested ternary expressions, wrapping them over multiple lines improves readability. Since continuation lines are still part of the same expression, 2 indent levels should be used instead of one.

GDScript allows wrapping statements using multiple lines using parentheses or backslashes. Parentheses are favored in this style guide since they make for easier refactoring. With backslashes, you have to ensure that the last line never contains a backslash at the end. With parentheses, you don't have to worry about the last line having a backslash at the end.

When wrapping a conditional expression over multiple lines, the and/or keywords should be placed at the beginning of the line continuation, not at the end of the previous line.

Правильно:

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

Уникайте непотрібних дужок

Avoid parentheses in expressions and conditional statements. Unless necessary for order of operations or wrapping over multiple lines, they only reduce readability.

Правильно:

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 the selected code commented, press Ctrl + K. This feature adds a single # sign at the start of 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. Загальнодоступні йдуть перед приватними.

  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 reference.

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.

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

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

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

Потім записуйте константи, експортовані змінні, загальнодоступні, приватні та 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 оцінює готові змінні безпосередньо перед зворотним викликом _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 підтримує необов'язкову статичну типізацію.

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

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

var health: int = 0

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

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

Alternatively, you can use the as keyword to cast the return type, and that type will be used to infer the type of the var.

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

This option is also considered more type-safe than the first.

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

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