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 є високорівневим, об’єктно-орієнтованим, імперативним і поступово введена мова програмування, створена для Godot. Він використовує синтаксис на основі відступів, схожий на такі мови, як Python. Його метою є оптимізація та тісна інтеграція з Godot Engine, що забезпечує велику гнучкість для створення та інтеграції вмісту.

GDScript повністю не залежить від Python і не базується на ньому.

Історія

Примітка

Документація про історію GDScript була переміщена до FAQ.

Зразок GDScript

Деякі люди можуть навчитися краще, поглянувши на синтаксис, тому ось приклад того, як виглядає GDScript.

# Everything after "#" is a comment.
# A file is a class!

# (optional) icon to show in the editor dialogs:
@icon("res://path/to/optional/icon.svg")

# (optional) class definition:
class_name MyClass

# Inheritance:
extends BaseClass


# Member variables.
var a = 5
var s = "Hello"
var arr = [1, 2, 3]
var dict = {"key": "value", 2: 3}
var other_dict = {key = "value", other_key = 2}
var typed_var: int
var inferred_type := "String"

# Constants.
const ANSWER = 42
const THE_NAME = "Charly"

# Enums.
enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
enum Named {THING_1, THING_2, ANOTHER_THING = -1}

# Built-in vector types.
var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)


# Function, with a default value for the last parameter.
func some_function(param1, param2, param3 = 123):
    const local_const = 5

    if param1 < local_const:
        print(param1)
    elif param2 > 5:
        print(param2)
    else:
        print("Fail!")

    for i in range(20):
        print(i)

    while param2 != 0:
        param2 -= 1

    match param3:
        3:
            print("param3 is 3!")
        _:
            print("param3 is not 3!")

    var local_var = param1 + 3
    return local_var


# Functions override functions with the same name on the base/super class.
# If you still want to call them, use "super":
func something(p1, p2):
    super(p1, p2)


# It's also possible to call another function in the super class:
func other_something(p1, p2):
    super.something(p1, p2)


# Inner class
class Something:
    var a = 10


# Constructor
func _init():
    print("Constructed!")
    var lv = Something.new()
    print(lv.a)

Якщо у вас є досвід роботи зі статично типізованими мовами, такими як C, C++, або C#, але раніше ви ніколи не використовували динамічно типізовані, радимо прочитати цей урок: GDScript: Вступ до мов динамічного типу.

Ідентифікатори

Будь-який рядок, що обмежується алфавітними символами ( від a до z і від A до Z), цифрами (від 0 до 9) і _ кваліфікується як ідентифікатор. Крім того, ідентифікатори не повинні починатися з цифри. Ідентифікатори залежать від регістру (foo відрізняється від FOO).

Ідентифікатори також можуть містити більшість символів Unicode, що є частиною UAX#31. Це дозволяє використовувати імена ідентифікаторів, написані не англійською мовою. Символи Юнікоду, які вважаються "сплутаними" з символами ASCII та емодзі, не допускаються в ідентифікаторах.

Ключові слова

Далі наведено список ключових слів, які підтримуються мовою. Оскільки ключові слова є зарезервованими словами (лексемами), їх не можна використовувати як ідентифікатори. Оператори (такі як in, not, and, або or) і імена вбудованих типів, перерахованих в наступних розділах, також зарезервовані.

Ключові слова визначаються в GDScript tokenizer, якщо ви хочете зазирнути під капот.

Ключове слово

Опис

if

Дивіться if/else/elif.

elif

Дивіться if/else/elif.

else

Дивіться if/else/elif.

for

Дивіться for.

while

Дивіться while.

match

Дивіться match.

when

Використовується охоронцями шаблонів <Pattern guards>`_ в операторах match.

перерва

Перериває виконання поточного циклу for, або while.

продовжити

Негайний перехід до наступної ітерації циклу for, або while.

pass

Використовується там, де, за правилами синтаксису, обов'язкова наявність інструкції (коду), але виконання коду небажане, наприклад, у порожніх функціях.

return

Повертає значення з функції.

class

Визначає внутрішній клас. Див. Внутрішні класи.

class_name

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

extends

Визначає який клас розширити поточним класом.

is

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

in

Перевіряє, чи знаходиться значення в рядку, масиві, діапазоні, словнику чи вузлі. Якщо використовується з for, він виконує їх ітерацію замість тестування.

as

Якщо можливо, то переводить значення в заданий тип.

self

Посилається на поточний екземпляр класу. Дивіться self.

super

Вирішує область батьківського методу. Див. Наслідування.

signal

Визначає сигнал. Дивіться Сигнали.

func

Визначає функцію. Див. Функції.

static

Визначає статичну функцію або статичну змінну-член.

const

Визначає константу. Див. Константи.

enum

Визначає перелік. Перегляньте Enums.

var

Визначає змінну. Див. Змінні.

breakpoint

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

preload

Попередньо завантажує клас, чи змінну. Дивіться Classes as resources.

await

Очікує на завершення сигналу або співпрограми. Див. Сигнали очікування або співпрограми.

yield

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

assert

Затверджує умову, реєструє помилку під час збою. Ігнорується в не-налагоджених збірках. Дивіться Assert keyword.

Void

Використовується для позначення того, що функція не повертає жодного значення.

PI

Константа Пі (π).

TAU

Константа Тау (τ).

INF

Стала нескінченності. Використовується для порівнянь і як результат розрахунків.

NAN

Константа NAN (не число). Використовується як неможливий результат розрахунків.

Оператори

Нижче наведено список підтримуваних операторів та їх пріоритет. Усі бінарні оператори є лівоасоціативними, включаючи оператор **. Це означає, що 2 ** 2 ** 3 дорівнює (2 ** 2) ** 3. Використовуйте дужки, щоб явно вказати потрібний пріоритет, наприклад 2 ** (2 ** 3). Потрійковий оператор if/else є правоасоціативним.

Оператор

Опис

( )

Групування (найвищий пріоритет)

Дужки насправді не є оператором, але дозволяють явно вказати пріоритет операції.

x[index]

Підписка

x.attribute

Посилання на атрибут

foo()

Виклик функції

await x

Сигнали очікування або співпрограми

x is Node
x is not Node

Перевірка типу

Дивіться також функцію is_instance_of().

x ** y

потужність

Помножує x на себе y разів, подібно до виклику функції pow().

~x

Побітове NOT (НЕ)

+x
-x

Тотожність / Заперечення

x * y
x / y
x % y

Множення/Ділення/Залишок ділення

Оператор % додатково використовується для format strings.

Примітка. Ці оператори мають таку саму поведінку, що й C++, що може бути неочікуваним для користувачів, які використовують Python, JavaScript тощо. Див. детальну примітку після таблиці.

x + y
x - y

Додавання (або об'єднання) / віднімання

x << y
x >> y

Побітовий зсув (зміщення)

x & y

Побітове AND (І)

x ^ y

Побітове XOR (Виключене АБО)

x | y

Побітове OR (АБО)

x == y
x != y
x < y
x > y
x <= y
x >= y

Порівняння

Дивіться детальну примітку після таблиці.

x in y
x not in y

Перевірка включення

in також використовується з ключовим словом for як частина синтаксису.

not x
!x

Логічне значення NOT і його unrecommended

x and y
x && y

Логічне І та його unrecommended

x or y
x || y

Логічне АБО та його unrecommended

true_expr if cond else false_expr

Потрійне if/else (якщо/інакше)

x as Node

Приведення типів

x = y
x += y
x -= y
x *= y
x /= y
x **= y
x %= y
x &= y
x |= y
x ^= y
x <<= y
x >>= y

Присвоювання значення, найнижчий пріоритет

Ви не можете використовувати оператор присвоювання всередині виразу.

Примітка

Поведінка деяких операторів може відрізнятися від очікуваної:

  1. Якщо обидва операнди оператора / мають тип int, то виконується цілочисельне ділення замість дробового. Наприклад, 5 / 2 == 2, а не 2.5. Якщо це небажано, використовуйте принаймні один літерал float (x / 2.0), приведення типів (float(x) / y) або множення на 1.0 (x * 1.0 / y).

  2. Оператор % доступний лише для int, для float використовуйте функцію fmod().

  3. Для від’ємних значень оператор % і fmod() використовують усічення замість округлення до від’ємної нескінченності. Це означає, що остача має знак. Якщо вам потрібен залишок у математичному сенсі, замість цього використовуйте функції posmod() і fposmod().

  4. Оператори == і != іноді дозволяють порівнювати значення різних типів (наприклад, 1 == 1.0 є істинним), але в інших випадках це може викликати помилку виконання. Якщо ви не впевнені щодо типів операндів, ви можете сміливо використовувати функцію is_same() (але зауважте, що вона більш сувора щодо типів і посилань). Для порівняння плаваючих значень замість цього використовуйте функції is_equal_approx() і is_zero_approx().

Літерали

Приклад(и)

Опис

null

Нульове значення

false, true

Логічні значення

45

Ціле число за десятковою системою числення

0x8f51

Ціле число на основі шістнадцяткової системи числення

0b101010

Ціле число в двійковій (бінарній) системі числення

3.14, 58.1e-10

Число з рухомою комою (дійсне)

"Hello", 'Hi'

Звичайні рядки

"""Hello""", '''Hi'''

Звичайні рядки в потрійних лапках

r"Hello", r'Hi'

Необроблені рядки

r"""Hello""", r'''Hi'''

Необроблені рядки в потрійних лапках

&"name"

StringName

^"Node/Label"

NodePath

Є також дві конструкції, які виглядають як літерали, але насправді такими не є:

Приклад

Опис

$NodePath

Скорочення для get_node("NodePath")

%UniqueNode

Скорочення для get_node("%UniqueNode")

Цілі числа та числа з плаваючою комою можуть бути розділені символом _ для кращої читабельності. Підходять такі способи запису чисел:

12_345_678  # Equal to 12345678.
3.141_592_7  # Equal to 3.1415927.
0x8080_0000_ffff  # Equal to 0x80800000ffff.
0b11_00_11_00  # Equal to 0b11001100.

Звичайні рядкові літерали можуть містити такі керуючі послідовності:

Escape послідовності

Розширюються

\n

Новий рядок (перенесення рядка)

\t

Горизонтальний символ табуляції

\r

Повернення каретки

\a

Попередження (звуковий сигнал/дзвінок)

\b

Стерти

\f

Розрив сторінки форми

\v

Вертикальний символ табуляції

\"

Подвійні лапки

\'

Одинарні лапки

\\

Зворотний слеш

\uXXXX

UTF-16 кодова точка Unicode XXXX (шістнадцяткове число, регістр не залежить)

\UXXXXXX

UTF-32 Кодова точка Юнікоду XXXXXX (шістнадцяткове число, регістр не залежить)

Є два способи представити екранований символ Unicode над 0xFFFF:

Крім того, використання \ з наступним символом нового рядка всередині рядка дозволить вам продовжити його в наступному рядку, не вставляючи символ нового рядка в самому рядку.

Рядок, укладений у лапки одного типу (наприклад "), може містити лапки іншого типу (наприклад `) без екранування. Рядки з потрійними лапками дозволяють уникнути екранування двох послідовних лапок одного типу (якщо вони не примикають до країв рядка).

Необроблені рядкові літерали завжди кодують рядок так, як він відображається у вихідному коді. Це особливо корисно для регулярних виразів. Літерал необробленого рядка не обробляє escape-послідовності, однак він розпізнає \\ і \" (\') і замінює їх собою. Таким чином, рядок може містити лапки який збігається з початковим, але лише якщо йому передує зворотна скісна риска.

print("\tchar=\"\\t\"")  # Prints `    char="\t"`.
print(r"\tchar=\"\\t\"") # Prints `\tchar=\"\\t\"`.

Примітка

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

GDScript також підтримує format strings.

Анотації

Анотації — це спеціальні токени в GDScript, які діють як модифікатори цілого скрипта, оголошення, оператора або місця у вихідному коді. Анотації можуть впливати на те, як скрипт обробляється редактором Godot та компілятором GDScript.

Кожна анотація починається із символу @ і вказується назвою. Детальний опис і приклад для кожної анотації можна знайти в посиланні на клас GDScript.

Наприклад, ви можете використовувати його для експорту значення до редактора:

@export_range(1, 100, 1, "or_greater")
var ranged_var: int = 50

Щоб дізнатися більше про експорт властивостей, прочитайте статтю GDScript exports.

Будь-який константний вираз, сумісний з необхідним типом аргументу, можна передати як значення аргументу анотації:

const MAX_SPEED = 120.0

@export_range(0.0, 0.5 * MAX_SPEED)
var initial_speed: float = 0.25 * MAX_SPEED

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

Обидва ці речі однакові:

@annotation_a
@annotation_b
var variable

@annotation_a @annotation_b var variable

анотація @onready

При використанні вузлів бажання зберігати посилання на частини сцени в змінній цілком нормальне. Оскільки сцени можуть бути налаштовані лише під час входу в дерево активних сцен, під-вузли можна отримати лише тоді, коли здійснюється виклик Node._ready().

var my_label


func _ready():
    my_label = get_node("MyLabel")

Це може бути трохи громіздким, особливо коли вузли та зовнішні посилання накопичуються. Для цього GDScript має анотацію @onready, яка відкладає ініціалізацію змінної-члена до виклику _ready(). Вона може замінити наведений вище код одним рядком:

@onready var my_label = get_node("MyLabel")

Попередження

Застосування анотації @onready та будь-якої анотації @export до тієї ж змінної не працює належним чином. Анотація @onready призведе до встановлення значення за замовчуванням після набрання чинності @export та замінить його:

@export var a = "init_value_a"
@onready @export var b = "init_value_b"

func _init():
    prints(a, b) # init_value_a <null>

func _notification(what):
    if what == NOTIFICATION_SCENE_INSTANTIATED:
        prints(a, b) # exported_value_a exported_value_b

func _ready():
    prints(a, b) # exported_value_a init_value_b

Тому створюється попередження ONREADY_WITH_EXPORT, яке за замовчуванням розглядається як помилка. Ми не рекомендуємо вимикати або ігнорувати його.

Коментарі

Все що знаходиться після символа #, і до кінця рядка, ігнорується і вважається коментарем.

# This is a comment.

Порада

У редакторі скриптів Godot спеціальні ключові слова виділено в коментарях, щоб привернути увагу користувача до конкретних коментарів:

  • Критично (відображається червоним кольором): ALERT, ATTENTION, CAUTION, CRITICAL, DANGER, SECURITY

  • Попередження (відображається жовтим кольором): BUG, DEPRECATED, FIXME, HACK, TASK, TBD, TODO, WARNING

  • Примітка (з’являється зеленим кольором): INFO, NOTE, NOTICE, TEST, TESTING

Ці ключові слова чутливі до регістру, тому їх потрібно писати у верхньому регістрі, щоб їх розпізнати:

# In the example below, "TODO" will appear in yellow by default.
# The `:` symbol after the keyword is not required, but it's often used.

# TODO: Add more items for the player to choose from.

Список виділених ключових слів та їхні кольори можна змінити в розділі Текстовий редактор > Тема > Маркери коментарів у налаштуваннях редактора.

Використовуйте два хеш-символи (##) замість одного (#), щоб додати коментар до документації, який з’являтиметься в документації сценарію та в описі інспектора експортованої змінної. Коментарі до документації мають бути розміщені безпосередньо над документованим елементом (наприклад, змінною-членом) або у верхній частині файлу. Також доступні спеціальні параметри форматування. Перегляньте Коментарі до документації GDScript для деталей.

## This comment will appear in the script documentation.
var value

## This comment will appear in the inspector tooltip, and in the documentation.
@export var exported_value

Кодові регіони

Області коду — це спеціальні типи коментарів, які редактор сценаріїв розуміє як згортані області. Це означає, що після написання коментарів регіону коду ви можете згортати та розгортати регіон, натиснувши стрілку, яка з’являється ліворуч від коментаря. Ця стрілка відображається в фіолетовому квадраті, щоб її можна було відрізнити від стандартного згортання коду.

Синтаксис такий:

# Important: There must be *no* space between the `#` and `region` or `endregion`.

# Region without a description:
#region
...
#endregion

# Region with a description:
#region Some description that is displayed even when collapsed
...
#endregion

Порада

Щоб швидко створити область коду, виберіть кілька рядків у редакторі сценаріїв, клацніть виділення правою кнопкою миші та виберіть Створити область коду. Опис регіону буде автоматично вибрано для редагування.

Області коду можна вкладати в інші області коду.

Ось конкретний приклад використання регіонів коду:

# This comment is outside the code region. It will be visible when collapsed.
#region Terrain generation
# This comment is inside the code region. It won't be visible when collapsed.
func generate_lakes():
    pass

func generate_hills():
    pass
#endregion

#region Terrain population
func place_vegetation():
    pass

func place_roads():
    pass
#endregion

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

Примітка

Окремі функції та розділи з відступами (такі як if і for) можна завжди згорнути в редакторі сценаріїв. Це означає, що вам слід уникати використання області коду для однієї функції або розділу з відступом, оскільки це не принесе великої користі. Області коду працюють найкраще, коли вони використовуються для групування кількох елементів разом.

Продовження лінії

Рядок коду в GDScript можна продовжити в наступному рядку за допомогою зворотної косої риски (\). Додайте один у кінці рядка, і код у наступному рядку діятиме так, ніби там знаходиться зворотна коса риска. Ось приклад:

var a = 1 + \
2

Рядок можна продовжувати кілька разів так:

var a = 1 + \
4 + \
10 + \
4

Вбудовані типи

Вбудовані типи розміщуються в стеку. Вони передаються як значення. Це означає, що копія створюється при кожному присвоюванні або при передачі їх як аргументів у функції. Виняток становлять Object, Array, Dictionary та упаковані масиви (такі як PackedByteArray), які передаються за посиланням, тому вони є спільними. Всі масиви, Dictionary і деякі об'єкти (Node, Resource) мають метод duplicate(), який дозволяє зробити копію.

Базові вбудовані типи

Змінна в GDScript може бути призначена декільком вбудованим типам.

нуль

null це порожній тип даних, який не містить інформації та не може приймати інші значення.

Лише типи, які успадковуються від Object, можуть мати значення null (тому Object називають типом, що допускає нульові значення). Variant types повинні завжди мати дійсне значення, тому не можуть мати значення null.

bool (логічні)

Скорочене для "boolean", може містити лише true, або false.

int

Скорочено від "ціле число", він зберігає цілі числа (додатні та від’ємні). Воно зберігається як 64-розрядне значення, еквівалентне int64_t у C++.

float

Зберігає дійсні числа, включаючи десяткові дроби, використовуючи значення з плаваючою комою. Воно зберігається як 64-розрядне значення, еквівалентне double у C++. Примітка: наразі такі структури даних, як Vector2, Vector3 і PackedFloat32Array зберігають 32-розрядні значення float одинарної точності.

String

Послідовність символів у форматі Unicode.

StringName

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

NodePath

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

Векторні вбудовані типи

Vector2

2D векторний тип містить поля x і y. Також можна отримати доступ до масиву.

Vector2i

Те саме, що Vector2, але компоненти є цілими числами. Корисно для представлення елементів у двовимірній сітці.

Rect2

Тип Прямокутник 2D, містить два векторних поля: position і size. Також містить поле end, яке є .position + size.

Vector3

Тип 3D вектора, містить поля x, y і z. Його також можна отримати як масив.

Vector3i

Те саме, що Vector3, але компоненти є цілими числами. Можна використовувати для індексування елементів у тривимірній сітці.

Transform2D

3 × 2 матриця, що використовується для 2D перетворень.

Plane

Тип 3D площини в нормалізованому вигляді, містить векторне поле normal і скалярну відстань d.

Quaternion

Quaternion - це тип даних, який використовується для представлення тривимірного обертання. Корисний для інтерполяції обертів.

AABB

Вісь вирівняна обмежувальною коробкою (або 3D коробкою) містить 2 векторних поля: position і size. Також містить поле end, яке є position + size.

Basis

Матриця 3x3, використовується для 3D обертання та масштабування. Вона містить 3 векторних полів (x, y і z) , а також може бути доступна в вигляді масиву 3D векторів.

Transform3D

3D-трансформація містить поле Basis basis та поле Vector3 origin.

Вбудовані типи движка

Color

Color (Колір) тип даних містить поля r, g, b і a. Також можуть бути доступні h, s і v для hue(відтінок)/ saturation(насиченість) / value(значення).

RID

Ідентифікатор ресурсу (RID). Сервери використовують загальні RID для посилання непрозорих даних.

Object

Базовий клас для всього, що не є вбудованим типом.

Вбудовані типи контейнерів

Array

Масиви - це послідовність об'єктів довільних типів, включаючи інші масиви, чи словники (дивіться нижче). Масив може динамічно змінювати розмір. Масиви індексуються, починаючи з індексу 0. Негативні показники рахуються з кінця.

var arr = []
arr = [1, 2, 3]
var b = arr[1] # This is 2.
var c = arr[arr.size() - 1] # This is 3.
var d = arr[-1] # Same as the previous line, but shorter.
arr[0] = "Hi!" # Replacing value 1 with "Hi!".
arr.append(4) # Array is now ["Hi!", 2, 3, 4].

Типізовані масиви

Godot також підтримує типізовані масиви. Під час операцій запису Godot перевіряє, чи значення елементів відповідають заданому типу, тому масив не може містити недійсні значення. Статичний аналізатор GDScript враховує типізовані масиви, проте методи масивів, такі як front() та back(), все ще мають тип повернення Variant.

Типова масиви мають синтаксис Array[Type], де Type може бути будь-яким типом Variant, власним або користувацьким класом, або переліком. Типи вкладених масивів (наприклад, Array[Array[int]] не підтримуються.

var a: Array[int]
var b: Array[Node]
var c: Array[MyClass]
var d: Array[MyEnum]
var e: Array[Variant]

Array і Array[Variant] — це те саме.

Примітка

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

Якщо ви хочете конвертувати типізований масив, ви можете створити новий масив і використати метод Array.assign():

var a: Array[Node2D] = [Node2D.new()]

# (OK) You can add the value to the array because `Node2D` extends `Node`.
var b: Array[Node] = [a[0]]

# (Error) You cannot assign an `Array[Node2D]` to an `Array[Node]` variable.
b = a

# (OK) But you can use the `assign()` method instead. Unlike the `=` operator,
# the `assign()` method copies the contents of the array, not the reference.
b.assign(a)

Єдиний виняток зроблено для типу Масив (Array[Variant]) для зручності користувача та сумісності зі старим кодом. Однак операції з нетиповими масивами вважаються небезпечними.

Упаковані масиви

Упаковані масиви, як правило, швидші для ітерації та модифікації порівняно з введеним масивом того самого типу (наприклад, PackedInt64Array проти Array[int]) і споживають менше пам’яті. У гіршому випадку очікується, що вони будуть такими ж швидкими, як і нетипізований масив. Навпаки, неупаковані масиви (типові чи ні) мають додаткові зручні методи, такі як Array.map, яких у PackedArrays немає. Зверніться до class reference, щоб дізнатися більше про доступні методи. Типізовані масиви, як правило, швидше обробляти та змінювати, ніж нетипізовані масиви.

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

  • PackedByteArray: масив байтів (цілі числа від 0 до 255).

  • PackedInt32Array: масив 32-розрядних цілих чисел.

  • PackedInt64Array: масив 64-розрядних цілих чисел.

  • PackedFloat32Array: масив 32-розрядних чисел з плаваючою точкою.

  • PackedFloat64Array: масив 64-розрядних чисел з плаваючою точкою.

  • PackedStringArray: масив рядків.

  • PackedVector2Array: масив значень Vector2.

  • PackedVector3Array: масив значень Vector3.

  • PackedVector4Array: Масив значень Vector4.

  • PackedColorArray: масив значень Color.

Dictionary

Асоціативний контейнер, який містить значення, на які посилаються унікальні ключі.

var d = {4: 5, "A key": "A value", 28: [1, 2, 3]}
d["Hi!"] = 0
d = {
    22: "value",
    "some_key": 2,
    "other_key": [2, 3, 4],
    "more_key": "Hello"
}

Також підтримується синтаксис таблиць у стилі Lua. У стилі Lua використовується = замість : і не використовуються лапки для позначення ключів рядків (що робить трохи менше для запису). Однак ключі, написані в цій формі, не можуть починатися з цифри (як і будь-який ідентифікатор GDScript), а повинні бути рядковими літералами.

var d = {
    test22 = "value",
    some_key = 2,
    other_key = [2, 3, 4],
    more_key = "Hello"
}

Щоб додати ключ до існуючого словника, зверніться до нього як до існуючого ключа та призначте йому:

var d = {} # Create an empty Dictionary.
d.waiting = 14 # Add String "waiting" as a key and assign the value 14 to it.
d[4] = "hello" # Add integer 4 as a key and assign the String "hello" as its value.
d["Godot"] = 3.01 # Add String "Godot" as a key and assign the value 3.01 to it.

var test = 4
# Prints "hello" by indexing the dictionary with a dynamic key.
# This is not the same as `d.test`. The bracket syntax equivalent to
# `d.test` is `d["test"]`.
print(d[test])

Примітка

Синтаксис дужок може використовуватися для доступу до властивостей будь-якого class_Object, а не лише до словників. Майте на увазі, що спроба індексувати неіснуючу властивість спричинить помилку скрипту . Щоб цього уникнути, використовуйте методи Object.get() та Object.set().

Типізовані словники

У Godot 4.4 додано підтримку типізованих словників. Під час операцій запису Godot перевіряє, чи відповідають ключі та значення елементів заданому типу, тому словник не може містити недійсні ключі або значення. Статичний аналізатор GDScript враховує типізовані словники. Однак методи словника, які повертають значення, все ще мають тип повернення Variant.

Типізовані словники мають синтаксис Dictionary[KeyType, ValueType], де KeyType та ValueType можуть бути будь-яким типом Variant, рідним або користувацьким класом, або переліком. Як ключ, так і тип значення обов'язково мають бути вказані, але ви можете використовувати Variant, щоб зробити будь-який з них нетипованим. Вкладені типізовані колекції (наприклад, Dictionary[String, Dictionary[String, int]]) не підтримуються.

var a: Dictionary[String, int]
var b: Dictionary[String, Node]
var c: Dictionary[Vector2i, MyClass]
var d: Dictionary[MyEnum, float]
# String keys, values can be any type.
var e: Dictionary[String, Variant]
# Keys can be any type, boolean values.
var f: Dictionary[Variant, bool]

Dictionary і Dictionary[Variant, Variant] є одним і тим самим.

Signal

Сигнал - це повідомлення, яке може передати об'єкт тим, хто хоче його прослухати. Тип сигналу можна використовувати для передачі випромінювача.

Сигнали краще використовувати, отримуючи їх від реальних об’єктів, напр. $Button.button_up.

Callable

Містить об’єкт і функцію, які корисні для передачі функцій як значень (наприклад, під час підключення до сигналів).

Отримання методу як члена повертає виклик. var x = $Sprite2D.rotate встановить значення x для виклику з $Sprite2D як об’єкт і rotate як метод.

Ви можете викликати його за допомогою методу call: x.call(PI).

Змінні

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

var a # Data type is 'null' by default.
var b = 5
var c = 3.8
var d = b + c # Variables are always initialized in direct order (see below).

Змінні, не обов'язково, але можуть мати специфікацію типу. Коли тип вказаний, змінна буде змушена завжди мати один і той самий тип, а спроба призначити несумісне значення призведе до помилки.

Типи задаються при створенні змінної з використанням символа : (двокрапка) після імені змінної, за яким вказується тип.

var my_vector2: Vector2
var my_node: Node = Sprite2D.new()

Якщо змінна ініціалізована в оголошенні, тип можна визначити, тому можна пропустити назву типу:

var my_vector2 := Vector2() # 'my_vector2' is of type 'Vector2'.
var my_node := Sprite2D.new() # 'my_node' is of type 'Sprite2D'.

Таке визначення типу можливе лише в тому випадку, якщо призначене значення має визначений тип, інакше виникне помилка.

Підтримувані типи:

  • Вбудовані типи (Array, Vector2, int, String, тощо).

  • Класи механізмів (Node, Resource, RefCounted тощо).

  • Імена констант, якщо вони містять ресурс скрипту (MyScript якщо ви визначили const MyScript = preload("res://my_script.gd")).

  • Інші класи в тому ж скрипті, з дотриманням меж застосування (InnerClass.NestedClass якщо ви визначили class NestedClass всередині class InnerClass в тих самих межах).

  • Класи скриптів, оголошені за допомогою ключового слова class_name.

  • Автозавантаження, зареєстровані як синглтони.

Примітка

Хоча Variant є дійсною специфікацією типу, це не справжній тип. Це лише означає, що немає встановленого типу, і еквівалентно відсутності статичного типу взагалі. Тому висновок не дозволений за замовчуванням для Variant, оскільки це, ймовірно, помилка.

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

Приклад ініціалізації

Змінні-члени ініціалізуються в такому порядку:

  1. Залежно від статичного типу змінної, вона або є null (нетипізовані змінні та об'єкти), або має значення типу за замовчуванням (0 для int, false для bool тощо).

  2. Зазначені значення присвоюються в порядку змінних у сценарії, зверху вниз.

    • (Лише для класів, похідних від Node.) Якщо до змінної застосовано анотацію @onready, її ініціалізація відкладається до кроку 5.

  3. Якщо визначено, викликається метод _init().

  4. Під час створення екземплярів сцен і ресурсів призначаються експортовані значення.

  5. (Тільки для класів, похідних від Node) Змінні @onready ініціалізуються.

  6. (Тільки для класів, похідних від Node.) Якщо визначено, викликається метод _ready().

Попередження

Ви можете вказати складний вираз як ініціалізатор змінних, включаючи виклики функцій. Переконайтеся, що змінні ініціалізовано в правильному порядку, інакше ваші значення можуть бути перезаписані. Наприклад:

var a: int = proxy("a", 1)
var b: int = proxy("b", 2)
var _data: Dictionary = {}

func proxy(key: String, value: int):
    _data[key] = value
    print(_data)
    return value

func _init() -> void:
    print(_data)

Надрукую:

{ "a": 1 }
{ "a": 1, "b": 2 }
{  }

Щоб виправити це, перемістіть визначення змінної _data над визначенням a або видаліть порожнє призначення словника (= {}).

Статичні змінні

Змінну-член класу можна оголосити статичною:

static var a

Статичні змінні належать до класу, а не до екземплярів. Це означає, що статичні змінні мають спільні значення для кількох екземплярів, на відміну від звичайних змінних-членів.

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

Примітка

Анотації @export і @onready не можна застосувати до статичної змінної. Локальні змінні не можуть бути статичними.

У наступному прикладі визначено клас Person зі статичною змінною з назвою max_id. Ми збільшуємо max_id у функції _init(). Це полегшує відстеження кількості екземплярів Person у нашій грі.

# person.gd
class_name Person

static var max_id = 0

var id
var name

func _init(p_name):
    max_id += 1
    id = max_id
    name = p_name

У цьому коді ми створюємо два екземпляри нашого класу Person і перевіряємо, що клас і кожен екземпляр мають однакове значення max_id, оскільки змінна є статичною та доступною для кожного примірника.

# test.gd
extends Node

func _ready():
    var person1 = Person.new("John Doe")
    var person2 = Person.new("Jane Doe")

    print(person1.id) # 1
    print(person2.id) # 2

    print(Person.max_id)  # 2
    print(person1.max_id) # 2
    print(person2.max_id) # 2

Статичні змінні можуть мати підказки типів, сеттери та геттери:

static var balance: int = 0

static var debt: int:
    get:
        return -balance
    set(value):
        balance = -value

До статичної змінної базового класу також можна отримати доступ через дочірній клас:

class A:
    static var x = 1

class B extends A:
    pass

func _ready():
    prints(A.x, B.x) # 1 1
    A.x = 2
    prints(A.x, B.x) # 2 2
    B.x = 3
    prints(A.x, B.x) # 3 3

Примітка

Під час посилання на статичну змінну зі скрипта інструменту, інший скрипт, що містить статичну змінну, також повинен бути скриптом інструменту. Див. Running code in the editor для отримання детальної інформації.

Анотація @static_unload

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

Попередження

Наразі через помилку сценарії ніколи не звільняються, навіть якщо використовується анотація @static_unload.

Зверніть увагу, що @static_unload застосовується до всього скрипта (включно з внутрішніми класами) і має бути розміщений на початку скрипта, перед class_name та extends:

@static_unload
class_name MyNode
extends Node

Дивіться також Статичні функції і Статичний конструктор.

Кастинг

Значення, присвоєні типізованим змінним, повинні мати сумісний тип. Якщо потрібно примусити значення бути певного типу, зокрема для типів об'єктів, ви можете використовувати оператор as.

Оператор спрацює вірно, якщо тип присвоєного об'єкта збігається з типом змінної, або є підтипом її типу.

var my_node2D: Node2D
my_node2D = $Sprite2D as Node2D # Works since Sprite2D is a subtype of Node2D.

Якщо значення не є підтипом, ця операція призведе до значення null.

var my_node2D: Node2D
my_node2D = $Button as Node2D # Results in 'null' since a Button is not a subtype of Node2D.

Вбудовані типи будуть примусово перетворені, якщо це можливо, в противному випадку движок видасть помилку.

var my_int: int
my_int = "123" as int # The string can be converted to int.
my_int = Vector2() as int # A Vector2 can't be converted to int, this will cause an error.

Приведення типів також корисне для кращої типобезпечності змінних під час взаємодії з деревом сцени:

# Will infer the variable to be of type Sprite2D.
var my_sprite := $Character as Sprite2D

# Will fail if $AnimPlayer is not an AnimationPlayer, even if it has the method 'play()'.
($AnimPlayer as AnimationPlayer).play("walk")

Константи

Константи - це значення, які ви не можете змінити під час роботи гри. Їх значення має бути відоме під час компіляції. Використання ключового слова const дозволяє надати значенню константи назву. Спроба присвоїти значення константі після її оголошення буде давати вам помилку.

Ми рекомендуємо використовувати константи для значень які не призначені для зміни.

const A = 5
const B = Vector2(20, 20)
const C = 10 + 20 # Constant expression.
const D = Vector2(20, 30).x # Constant expression: 20.
const E = [1, 2, 3, 4][0] # Constant expression: 1.
const F = sin(20) # 'sin()' can be used in constant expressions.
const G = x + 20 # Invalid; this is not a constant expression!
const H = A + 20 # Constant expression: 25 (`A` is a constant).

Хоча тип констант визначається з присвоєного значення, також можливо додати явну специфікацію типу:

const A: int = 5
const B: Vector2 = Vector2()

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

Ви також можете створити константи всередині функції, що корисно для іменування локальних магічних значень.

Переліки

Переліки - це, в основному, скорочення для констант, і вони дуже корисні, якщо ви хочете призначити послідовні цілі числа якимось константам.

enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

# Is the same as:
const TILE_BRICK = 0
const TILE_FLOOR = 1
const TILE_SPIKE = 2
const TILE_TELEPORT = 3

If you pass a name to the enum, it will put all the keys inside a constant Dictionary of that name. This means all constant methods of a dictionary can also be used with a named enum. This only works for GDScript enums, not for enums from built-in classes.

Важливо

Ключі в іменованому переліку не реєструються як глобальні константи. Доступ до них має здійснюватися з префіксом імені переліку (Name.KEY).

enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT}

# Is the same as:
const State = {STATE_IDLE = 0, STATE_JUMP = 5, STATE_SHOOT = 6}
# Access values with State.STATE_IDLE, etc.

func _ready():
    # Access values with Name.KEY, prints '5'
    print(State.STATE_JUMP)
    # Use dictionary methods:
    # prints '["STATE_IDLE", "STATE_JUMP", "STATE_SHOOT"]'
    print(State.keys())
    # prints '{ "STATE_IDLE": 0, "STATE_JUMP": 5, "STATE_SHOOT": 6 }'
    print(State)
    # prints '[0, 5, 6]'
    print(State.values())

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

Функції

Функції завжди належать до клас. Пріоритет області для пошуку змінних: локальний → член класу → глобальний. Змінна self завжди доступна та надається як опція для доступу до членів класу (див. self), але не завжди потрібна (і не повинна надсилатися як перший аргумент функції, на відміну від Python).

func my_function(a, b):
    print(a)
    print(b)
    return a + b  # Return is optional; without it 'null' is returned.

Функція може повертати значення (return) в будь-якій точці. За замовчуванням повертається - null.

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

# Since the last two parameters are optional, all these calls are valid:
# - my_function(1)
# - my_function(1, 20)
# - my_function(1, 20, 100)
func my_function(a_required, b_optional = 10, c_optional = 42):
    print(a_required)
    print(b_optional)
    print(c_optional)

Якщо функція містить лише один рядок коду, її можна записати в одному рядку:

func square(a): return a * a

func hello_world(): print("Hello World")

func empty_function(): pass

Функції також можуть мати специфікацію типу для аргументів та для повертаного значення. Типи для аргументів можна додавати подібно до змінних:

func my_function(a: int, b: String):
    pass

Якщо аргумент функції має значення за замовчуванням, можна визначити його тип:

func my_function(int_arg := 42, String_arg := "string"):
    pass

Тип повернення функції можна вказати після списку аргументів за допомогою токена стрілки (->):

func my_int_function() -> int:
    return 0

Функції, що мають тип повернення, повинні повертати значення відповідного типу. Тип void означає, що функція нічого не повертає. Функції можуть використовувати ключове слово return, але вони не можуть повертати ніякого значення.

func void_function() -> void:
    return # Can't return a value.

Примітка

Функції, які повертають інший тип, завжди повинні повертати якесь значення, тому якщо у вашому коді є операції розгалуження (наприклад конструкцію if/else), усі можливі шляхи повинні мати повернення. Наприклад, якщо у вас return всередині блоку if, але не після нього, редактор викличе помилку, оскільки якщо умова блоку не буде виконана, функція не матиме значення для повернення.

Посилання на функції

Функції є значеннями першого класу з точки зору об’єкта Callable. Посилання на функцію за іменем без її виклику автоматично генерує правильний виклик. Це можна використовувати для передачі функцій як аргументів.

func map(arr: Array, function: Callable) -> Array:
    var result = []
    for item in arr:
        result.push_back(function.call(item))
    return result

func add1(value: int) -> int:
    return value + 1;

func _ready() -> void:
    var my_array = [1, 2, 3]
    var plus_one = map(my_array, add1)
    print(plus_one) # Prints `[2, 3, 4]`.

Примітка

Об’єкти, що викликаються, повинні викликатися методом call(). Ви не можете безпосередньо використовувати оператор (). Ця поведінка реалізована, щоб уникнути проблем із продуктивністю під час прямих викликів функцій.

Лямбда-функції

Лямбда-функції дозволяють оголошувати функції, які не належать до класу. Замість цього створюється об’єкт Callable, який безпосередньо призначається змінній. Це може бути корисним для створення викликів для передачі без забруднення області класу.

var lambda = func (x):
    print(x)

Щоб викликати створену лямбда-вираз, можна скористатися методом call():

lambda.call(42) # Prints `42`.

Лямбда-функції можна іменувати для цілей налагодження (назва відображається в налагоджувачі):

var lambda = func my_lambda(x):
    print(x)

Ви можете вказати підказки типів для лямбда-функцій так само, як і для звичайних:

var lambda := func (x: int) -> void:
    print(x)

Зверніть увагу, що якщо ви хочете повернути значення з лямбда-функції, потрібне явне вказівка return (не можна пропускати return):

var lambda = func (x): return x ** 2
print(lambda.call(2)) # Prints `4`.

Лямбда-функції фіксують локальне середовище:

var x = 42
var lambda = func ():
    print(x) # Prints `42`.
lambda.call()

Попередження

Локальні змінні захоплюються за значенням один раз, під час створення лямбда-виразу. Тому вони не будуть оновлені в лямбда-виразі, якщо їх перепризначити в зовнішній функції:

var x = 42
var lambda = func (): print(x)
lambda.call() # Prints `42`.
x = "Hello"
lambda.call() # Prints `42`.

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

var x = 42
var lambda = func ():
    print(x) # Prints `42`.
    x = "Hello" # Produces the `CONFUSABLE_CAPTURE_REASSIGNMENT` warning.
    print(x) # Prints `Hello`.
lambda.call()
print(x) # Prints `42`.

Однак, якщо ви використовуєте типи даних з передачею за посиланням (масиви, словники та об'єкти), то зміни вмісту будуть спільними, доки ви не перепризначите змінну:

var a = []
var lambda = func ():
    a.append(1)
    print(a) # Prints `[1]`.
    a = [2] # Produces the `CONFUSABLE_CAPTURE_REASSIGNMENT` warning.
    print(a) # Prints `[2]`.
lambda.call()
print(a) # Prints `[1]`.

Статичні функції

Функцію можна оголосити статичною. Коли функція є статичною, вона не має доступу до змінних-членів екземпляра або self. Статична функція має доступ до статичних змінних. Також статичні функції корисні для створення бібліотек допоміжних функцій:

static func sum2(a, b):
    return a + b

Лямбда-функції не можна оголосити статичними.

Дивіться також Статичні змінні і Статичний конструктор.

Варіадичні функції

Варіативна функція — це функція, яка може приймати змінну кількість аргументів. Починаючи з Godot 4.5, GDScript підтримує варіативні функції. Щоб оголосити варіативну функцію, потрібно використовувати параметр rest, який збирає всі зайві аргументи в масив.

func my_func(a, b = 0, ...args):
    prints(a, b, args)

func _ready():
    my_func(1)             # 1 0 []
    my_func(1, 2)          # 1 2 []
    my_func(1, 2, 3)       # 1 2 [3]
    my_func(1, 2, 3, 4)    # 1 2 [3, 4]
    my_func(1, 2, 3, 4, 5) # 1 2 [3, 4, 5]

Функція може мати щонайбільше один параметр-залишок, який має бути останнім у списку параметрів. Параметр-залишок не може мати значення за замовчуванням. Статичні та лямбда-функції також можуть бути змінними.

Статична типізація також працює для змінних функцій. Однак типізовані масиви наразі не підтримуються як статичний тип параметра rest:

# You cannot specify `...values: Array[int]`.
func sum(...values: Array) -> int:
    var result := 0
    for value in values:
        assert(value is int)
        result += value
    return result

Примітка

Хоча ви можете оголошувати функції як змінні за допомогою параметра rest, розпакування параметрів під час виклику функції з використанням синтаксису розгортання, який існує в деяких мовах програмування (JavaScript, PHP), наразі не підтримується в GDScript. Однак ви можете використовувати callv() для виклику функції з масивом аргументів:

func test_func(...args):
    #log_data(...args) # This won't work.
    log_data.callv(args) # This will work.

func log_data(...values):
    # You should use `callv()` if you want to pass `values` as the argument list,
    # rather than passing the array as the first argument.
    prints.callv(values)
    # You can use array concatenation to prepend/append the argument list.
    write_data.callv(["user://log.txt"] + values)

func write_data(path, ...values):
    # ...

Абстрактні функції

Див. Абстрактні класи та методи.

Оператори і управління потоком

Оператори є стандартними і можуть бути призначеннями, викликами функцій, структурами контрольного потоку тощо (дивіться нижче). Роздільник ; абсолютно необов'язковий.

Вирази

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

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

Ось кілька прикладів виразів:

2 + 2 # Binary operation.
-5 # Unary operation.
"okay" if x > 4 else "not okay" # Ternary operation.
x # Identifier representing variable or constant.
x.a # Attribute access.
x[4] # Subscript access.
x > 2 or x < 5 # Comparisons and logic operators.
x == y + 2 # Equality test.
do_something() # Function call.
[1, 2, 3] # Array definition.
{A = 1, B = 2} # Dictionary definition.
preload("res://icon.svg") # Preload builtin function.
self # Reference to current instance.

Ідентифікатори, атрибути та індекси є дійсними цілями призначення. Інші вирази не можуть бути в лівій частині завдання.

self

self можна використовувати для посилання на поточний екземпляр і часто еквівалентно прямому посиланню на символи, доступні в поточному сценарії. Проте self також дозволяє вам отримати доступ до властивостей, методів та інших імен, які визначені динамічно (тобто, як очікується, існуватимуть у підтипах поточного класу або надаються за допомогою _set() та/або _get()).

extends Node

func _ready():
    # Compile time error, as `my_var` is not defined in the current class or its ancestors.
    print(my_var)
    # Checked at runtime, thus may work for dynamic properties or descendant classes.
    print(self.my_var)

    # Compile time error, as `my_func()` is not defined in the current class or its ancestors.
    my_func()
    # Checked at runtime, thus may work for descendant classes.
    self.my_func()

Попередження

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

якщо/інакше/якщо-інакше (ельіф)

Прості умови створюються за допомогою синтаксису if/else/elif. Дужки навколо умов дозволені, але не обов'язкові. Враховуючи характер відступів на основі табуляції, elif можна використовувати замість else/if для підтримки рівня відступу.

if (expression):
    statement(s)
elif (expression):
    statement(s)
else:
    statement(s)

Короткі оператори можна записати в тому ж рядку, що й умову:

if 1 + 1 == 2: return 2 + 2
else:
    var x = 3 + 3
    return x

Іноді вам може знадобитися призначити інше початкове значення на основі булевого виразу. У цьому випадку стануть у пригоді тернарні вирази if:

var x = (value) if (expression) else (value)
y += 3 if y < 10 else -1

Тернарні вирази типу "if" можуть бути вкладеними для обробки більше ніж 2 випадків. Під час вкладення тернарних виразів типу "if" рекомендується розміщувати весь вираз на кількох рядках для збереження читабельності:

var count = 0

var fruit = (
        "apple" if count == 2
        else "pear" if count == 1
        else "banana" if count == 0
        else "orange"
)
print(fruit)  # banana

# Alternative syntax with backslashes instead of parentheses (for multi-line expressions).
# Less lines required, but harder to refactor.
var fruit_alt = \
        "apple" if count == 2 \
        else "pear" if count == 1 \
        else "banana" if count == 0 \
        else "orange"
print(fruit_alt)  # banana

Ви також можете перевірити, чи міститься значення всередині чогось. Для цього можна використовувати оператор if у поєднанні з оператором in:

# Check if a letter is in a string.
var text = "abc"
if 'b' in text: print("The string contains b")

# Check if a variable is contained within a node.
if "varName" in get_parent(): print("varName is defined in parent!")

while

Прості цикли створюються за допомогою синтаксису while. Цикли можна розірвати за допомогою break або продовжити за допомогою continue (що пропускає до наступної ітерації циклу без виконання подальшого коду в поточній ітерації):

while (expression):
    statement(s)

for

Для перебору діапазону, такого як масив або таблиця, використовується цикл for. Під час перебору масиву поточний елемент масиву зберігається у змінній циклу. Під час перебору словника ключ зберігається у змінній циклу.

for x in [5, 7, 11]:
    statement # Loop iterates 3 times with 'x' as 5, then 7 and finally 11.

var names = ["John", "Marta", "Samantha", "Jimmy"]
for name: String in names: # Typed loop variable.
    print(name) # Prints name's content.

var dict = {"a": 0, "b": 1, "c": 2}
for i in dict:
    print(dict[i]) # Prints 0, then 1, then 2.

for i in range(3):
    statement # Similar to [0, 1, 2] but does not allocate an array.

for i in range(1, 3):
    statement # Similar to [1, 2] but does not allocate an array.

for i in range(2, 8, 2):
    statement # Similar to [2, 4, 6] but does not allocate an array.

for i in range(8, 2, -2):
    statement # Similar to [8, 6, 4] but does not allocate an array.

for c in "Hello":
    print(c) # Iterate through all characters in a String, print every letter on new line.

for i in 3:
    statement # Similar to range(3).

for i in 2.2:
    statement # Similar to range(ceil(2.2)).

Якщо ви хочете призначити значення масиву під час його проходження, найкраще використовувати for i в array.size().

for i in array.size():
    array[i] = "Hello World"

Змінна циклу є локальною для циклу for, і її призначення не змінить значення в масиві. Об’єктами, переданими за посиланням (наприклад, вузлами), можна маніпулювати, викликаючи методи змінної циклу.

for string in string_array:
    string = "Hello World" # This has no effect

for node in node_array:
    node.add_to_group("Cool_Group") # This has an effect

match

Оператор match використовується для перемикання виконання програми. Він еквівалентний оператору switch, який зустрічається в багатьох інших мовах програмування, але пропонує деякі додаткові можливості.

Попередження

match є більш суворим за типом, ніж оператор ==. Наприклад, 1 не відповідатиме 1.0. Єдиним винятком є збіг String проти StringName: наприклад, рядок "hello" вважається рівним StringName &"hello".

Основний синтаксис

match <test value>:
    <pattern(s)>:
        <block>
    <pattern(s)> when <pattern guard>:
        <block>
    <...>

Експрес-курс для тих, хто знайомий з операторами перемикання

  1. Замініть switch на match.

  2. Видаліть case.

  3. Видаліть усі розривиs.

  4. Змініть default на одне підкреслення.

Контрольний потік

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

Примітка

Спеціальну поведінку continue у match, підтримувану в 3.x, було видалено в Godot 4.0.

Доступні наступні типи візерунків:

  • Літеральний візерунок

    Збігається з буквальний:

    match x:
        1:
            print("We are number one!")
        2:
            print("Two are better than one!")
        "test":
            print("Oh snap! It's a string!")
    
  • Шаблон виразу

    Збігається з константним виразом, ідентифікатором або доступом до атрибута (A.B):

    match typeof(x):
        TYPE_FLOAT:
            print("float")
        TYPE_STRING:
            print("text")
        TYPE_ARRAY:
            print("array")
    
  • Шаблон Wildcard

    Цей шаблон відповідає усьому. Він прописаний як окреме підкреслення.

    Його можна використовувати як еквівалент default в операторі switch іншими мовами:

    match x:
        1:
            print("It's one!")
        2:
            print("It's one times two!")
        _:
            print("It's not 1 or 2. I don't care to be honest.")
    
  • Зв'язувальний шаблон

    Шаблон зв'язування вводить нову змінну. Як і шаблон підстановки, він відповідає всьому, а також надає цьому значенню назву. Він особливо корисний у шаблонах масивів та словників:

    match x:
        1:
            print("It's one!")
        2:
            print("It's one times two!")
        var new_var:
            print("It's not 1 or 2, it's ", new_var)
    
  • Шаблон масиву

    Відповідає масиву. Кожен окремий елемент шаблону масиву сам по собі є шаблоном, тому їх можна вкладати.

    Спочатку тестується довжина масиву, він повинен бути того ж розміру, що і шаблон, інакше шаблон не збігатиметься.

    Масив відкритого типу: Масив може стати більшим за шаблон, під час виконання останнього під-шаблону ...

    Кожен під-шаблон повинен бути розділений комою.

    match x:
        []:
            print("Empty array")
        [1, 3, "test", null]:
            print("Very specific array")
        [var start, _, "test"]:
            print("First element is ", start, ", and the last is \"test\"")
        [42, ..]:
            print("Open ended array")
    
  • Словниковий шаблон

    Працює так само, як і шаблон масиву. Кожен ключ повинен бути постійним шаблоном.

    Спочатку тестується розмір словника, він повинен бути того ж розміру, що і шаблон, інакше шаблон не співладе.

    Відкритий словник: Словник може стати більшим за шаблон при виконанні останнього підшаблону ...

    Кожен під-шаблон повинен бути відокремлений комою.

    Якщо значення не вказано, перевіряється лише наявність ключа.

    Шаблон значення відокремлений від основного шаблону :.

    match x:
        {}:
            print("Empty dict")
        {"name": "Dennis"}:
            print("The name is Dennis")
        {"name": "Dennis", "age": var age}:
            print("Dennis is ", age, " years old.")
        {"name", "age"}:
            print("Has a name and an age, but it's not Dennis :(")
        {"key": "godotisawesome", ..}:
            print("I only checked for one entry and ignored the rest")
    
  • Кілька шаблонів

    Ви також можете вказати кілька шаблонів, розділених комою. Ці шаблони не можуть мати ніяких прив'язок у них.

    match x:
        1, 2, 3:
            print("It's 1 - 3")
        "Sword", "Splash potion", "Fist":
            print("Yep, you've taken damage")
    

Охоронці шаблону

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

Тільки одна гілка може бути виконана за одне match. Після вибору гілки решта не перевіряються. Якщо ви хочете використовувати той самий шаблон для кількох гілок або запобігти вибору гілки з надто загальним шаблоном, ви можете вказати захист шаблону після списку шаблонів за допомогою ключового слова when:

match point:
    [0, 0]:
        print("Origin")
    [_, 0]:
        print("Point on X-axis")
    [0, _]:
        print("Point on Y-axis")
    [var x, var y] when y == x:
        print("Point on line y = x")
    [var x, var y] when y == -x:
        print("Point on line y = -x")
    [var x, var y]:
        print("Point (%s, %s)" % [x, y])
  • Якщо для поточної гілки немає відповідного шаблону, захист шаблонів не оцінюється, а шаблони наступної гілки перевіряються.

  • Якщо знайдено відповідний шаблон, оцінюється захист шаблону.

    • Якщо це істина, тоді тіло гілки виконується, а збіг завершується.

    • Якщо воно невірне, то перевіряються шаблони наступної гілки.

Класи

За замовчуванням усі файли скриптів є безіменними класами. У цьому випадку ви можете посилатися на них лише за допомогою шляху до файлу, використовуючи або відносний, або абсолютний шлях. Наприклад, якщо ви назвете файл скрипта character.gd:

# Inherit from 'character.gd'.

extends "res://path/to/character.gd"

# Load character.gd and create a new node instance from it.

var Character = load("res://path/to/character.gd")
var character_node = Character.new()

Реєстрація іменованих класів

Ви можете дати своєму класу ім'я, щоб зареєструвати його як новий тип у редакторі Godot. Для цього використовується ключове слово class_name. За потреби можна використовувати анотацію @icon зі шляхом до зображення, щоб використовувати його як піктограму. Після цього ваш клас з'явиться в редакторі з новою піктограмою:

# item.gd

@icon("res://interface/icons/item.png")
class_name Item
extends Node
../../../_images/class_name_editor_register_example.png

Порада

SVG-зображення, які використовуються як спеціальні піктограми вузлів, повинні мати Редактор > Масштабувати за допомогою масштабу редактора та Редактор > Перетворити значки за допомогою теми редактора import options. Це дозволяє піктограмам відповідати параметрам масштабу та тематики редактора, якщо піктограми розроблено з тією ж палітрою кольорів, що й власні піктограми Godot.

Ось зразок файла класу:

# Saved as a file named 'character.gd'.

class_name Character


var health = 5


func print_health():
    print(health)


func print_this_script_three_times():
    print(get_script())
    print(ResourceLoader.load("res://character.gd"))
    print(Character)

Якщо ви також хочете використовувати extends, ви можете залишити обидва в одному рядку:

class_name MyNode extends Node

Іменовані класи реєструються глобально, що означає, що вони стають доступними для використання в інших скриптах без необхідності load або preload їх:

var player

func _ready():
    player = Character.new()

Примітка

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

Попередження

Редактор Godot приховає ці спеціальні класи з іменами, які починаються з префікса «Редактор» у діалогових вікнах «Створити новий вузол» або «Створити нову сцену». Класи доступні для створення екземплярів під час виконання через їхні імена класів, але автоматично приховані вікнами редактора разом із вбудованими вузлами редактора, які використовуються редактором Godot.

Абстрактні класи та методи

Починаючи з Godot 4.5, ви можете визначати абстрактні класи та методи за допомогою анотації @abstract.

Абстрактний клас — це клас, екземпляр якого не можна створити безпосередньо. Натомість він призначений для успадкування іншими класами. Спроба створити екземпляр абстрактного класу призведе до помилки.

Абстрактний метод — це метод, який не має реалізації. Тому після заголовка функції очікується новий рядок або крапка з комою. Це визначає контракт, якому повинні відповідати успадковувані класи, оскільки сигнатура методу має бути сумісною під час перевизначення.

Класи-успадковувачі повинні або забезпечувати реалізації для всіх абстрактних методів, або клас-успадковувач має бути позначений як абстрактний. Якщо клас має хоча б один абстрактний метод (або свій власний, або нереалізований успадкований), то він також має бути позначений як абстрактний. Однак зворотне не вірно: абстрактному класу дозволено не мати абстрактних методів.

Порада

Якщо ви хочете оголосити метод як необов'язковий для перевизначення, вам слід використовувати неабстрактний метод та надати реалізацію за замовчуванням.

Наприклад, ви можете мати абстрактний клас під назвою Shape, який визначає абстрактний метод під назвою draw(). Потім ви можете створити підкласи, такі як Circle та Square, які реалізують метод draw() по-своєму. Це дозволяє вам визначити спільний інтерфейс для всіх фігур без необхідності реалізовувати всі деталі в самому абстрактному класі:

@abstract class Shape:
    @abstract func draw()

# This is a concrete (non-abstract) subclass of Shape.
# You **must** implement all abstract methods in concrete classes.
class Circle extends Shape:
    func draw():
        print("Drawing a circle.")

class Square extends Shape:
    func draw():
        print("Drawing a square.")

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

@abstract
class_name AbstractClass
extends Node

@abstract class AbstractInnerClass:
    func _ready():
        pass

# This is an example of a concrete subclass of `AbstractInnerClass`.
# This class can be instantiated using `AbstractClass.ConcreteInnerClass.new()`
# in other scripts, even though it's part of an abstract `class_name` script.
class ConcreteInnerClass extends AbstractInnerClass:
    func _ready():
        print("Concrete class ready.")

Попередження

Оскільки абстрактний клас не може бути створений, його неможливо приєднати до вузла. Якщо ви спробуєте це зробити, рушій виведе помилку під час запуску сцени:

Cannot set object script. Script '<path to script>' should not be abstract.

Безіменовані класи також можна визначити як абстрактні, анотація @abstract повинна передувати extends:

@abstract
extends Node

Успадкування

Клас (зберігається як файл) може успадковувати від:

  • Глобального класу.

  • Іншого класу.

  • Внутрішнього класу всередині іншого файла класу.

Багаторазове успадкування неможливе.

Успадкування використовує ключове слово extends:

# Inherit/extend a globally available class.
extends SomeClass

# Inherit/extend a named class file.
extends "somefile.gd"

# Inherit/extend an inner class in another file.
extends "somefile.gd".SomeInnerClass

Примітка

Якщо успадкування не визначено явно, клас за умовчанням успадковуватиме class_RefCounted.

Щоб перевірити, чи успадковується даний екземпляр від даного класу, можна використовувати ключове слово is:

# Cache the enemy class.
const Enemy = preload("enemy.gd")

# [...]

# Use 'is' to check inheritance.
if entity is Enemy:
    entity.apply_damage()

Щоб викликати функцію в суперкласі (тобто в одному extend-(у вашому поточному класі), використовуйте super ключове слово:

super(args)

Це особливо корисно, оскільки функції в розширюючих класах замінюють функції з тим самим ім'ям у їхніх суперкласах. Якщо ви все ще хочете їх викликати, ви можете використовувати super:

func some_func(x):
    super(x) # Calls the same function on the super class.

Якщо вам потрібно викликати іншу функцію з суперкласу, ви можете вказати назву функції за допомогою оператора атрибута:

func overriding():
    return 0 # This overrides the method in the base class.

func dont_override():
    return super.overriding() # This calls the method as defined in the base class.

Попередження

Одним із поширених помилкових уявлень є спроба перевизначити невіртуальні методи двигуна, такі як get_class(), queue_free() тощо. Це не підтримується з технічних причин.

У Godot 3 ви можете затіняти методи двигуна в GDScript, і це працюватиме, якщо викликати цей метод у GDScript. Однак механізм не виконає ваш код, якщо метод викликається всередині механізму під час певної події.

У Godot 4 навіть затінення може не завжди працювати, оскільки GDScript оптимізує виклики рідних методів. Тому ми додали попередження NATIVE_METHOD_OVERRIDE, яке за умовчанням розглядається як помилка. Ми настійно рекомендуємо не вимикати або ігнорувати попередження.

Зауважте, що це не стосується віртуальних методів, таких як _ready(), _process() та інших (позначених кваліфікатором virtual у документації та назви починаються з підкреслення). Ці методи призначені спеціально для налаштування поведінки двигуна та можуть бути замінені в GDScript. Для цих цілей також можуть бути корисні сигнали та повідомлення.

Конструктор класу

Конструктор класу, який викликається під час створення екземпляра класу, називається _init. Якщо ви хочете викликати конструктор базового класу, ви також можете використовувати синтаксис super. Зверніть увагу, що кожен клас має неявний конструктор, який завжди викликається (визначає значення змінних класу за замовчуванням). super використовується для виклику явного конструктора:

func _init(arg):
   super("some_default", arg) # Call the custom base constructor.

Це краще пояснити на прикладах. Розглянемо такий скрипт:

# state.gd (inherited class).
var entity = null
var message = null


func _init(e = null):
    entity = e


func enter(m):
    message = m


# idle.gd (inheriting class).
extends "state.gd"


func _init(e = null, m = null):
    super(e)
    # Do something with 'e'.
    message = m

Тут слід зазначити таке:

  1. Якщо успадкований клас (state.gd) визначає конструктор _init, який приймає аргументи (e у цьому випадку), тоді успадкований клас (idle.gd) повинен також визначити _init та передати відповідні параметри до _init з state.gd.

  2. idle.gd може мати іншу кількість аргументів, ніж базовий клас state.gd.

  3. У наведеному вище прикладі e, переданий конструктору state.gd, є тим самим e, переданим idle.gd.

  4. Якщо конструктор _init класу idle.gd не приймає жодних аргументів, йому все одно потрібно передавати якесь значення базовому класу state.gd, навіть якщо він нічого не робить. Це підводить нас до того факту, що базовому конструктору можна передавати не лише змінні, а й вирази, наприклад:

# idle.gd

func _init():
    super(5)

Статичний конструктор

Статичний конструктор — це статична функція _static_init, яка викликається автоматично під час завантаження класу, після ініціалізації статичних змінних:

static var my_static_var = 1

static func _static_init():
    my_static_var = 2

Статичний конструктор не може приймати аргументи та не повинен повертати значення.

Внутрішні класи

Файл класу може містити внутрішні класи. Внутрішні класи визначаються за допомогою ключового слова class. Вони встановлені за допомогою функції ClassName.new().

# Inside a class file.

# An inner class in this class file.
class SomeInnerClass:
    var a = 5


    func print_value_of_a():
        print(a)


# This is the constructor of the class file's main class.
func _init():
    var c = SomeInnerClass.new()
    c.print_value_of_a()

Класи як ресурси

Класи, що зберігаються у вигляді файлів, обробляються як GDScripts. Для доступу до них в інших класах їх необхідно завантажити з диска. Це робиться за допомогою функцій load або preload (див. нижче). Створення екземпляра завантаженого ресурсу класу здійснюється викликом функції new на об'єкті класу:

# Load the class resource when calling load().
var MyClass = load("myclass.gd")

# Preload the class only once at compile time.
const MyClass = preload("myclass.gd")


func _init():
    var a = MyClass.new()
    a.some_function()

Експорт

Примітка

Документація про експорт переміщена до Експортовані властивості GDScript.

Властивості (сеттери та геттери)

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

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

Приклад:

var milliseconds: int = 0
var seconds: int:
    get:
        return milliseconds / 1000
    set(value):
        milliseconds = value * 1000

Примітка

На відміну від setget у попередніх версіях Godot, set і get методи завжди викликаються (окрім випадків, зазначених нижче), навіть коли до них звертаються всередині одного класу (з префіксом або без нього само.). Це робить поведінку послідовною. Якщо вам потрібен прямий доступ до значення, використовуйте іншу змінну для прямого доступу та зробіть так, щоб код властивості використовував це ім’я.

Альтернативний синтаксис

Також існує ще одна нотація для використання існуючих функцій класу, якщо ви хочете розділити код з оголошення змінної або вам потрібно повторно використовувати код для кількох властивостей (але ви не можете розрізнити, для якої властивості викликається сеттер/геттер):

var my_prop:
    get = get_my_prop, set = set_my_prop

Це також можна зробити в тому ж рядку:

var my_prop: get = get_my_prop, set = set_my_prop

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

Примітка

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

Коли сетер/гетер не викликається

Коли змінна ініціалізована, значення ініціалізатора буде записане безпосередньо в змінну. У тому числі, якщо до змінної застосовано анотацію @onready.

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

signal changed(new_value)
var warns_when_changed = "some value":
    get:
        return warns_when_changed
    set(value):
        changed.emit(value)
        warns_when_changed = value

Це також стосується альтернативного синтаксису:

var my_prop: set = set_my_prop

func set_my_prop(value):
    my_prop = value # No infinite recursion.

Попередження

Виняток не поширюється на інші функції, що викликаються в сеттері/геттері. Наприклад, наступний код викличе нескінченну рекурсію:

var my_prop:
    set(value):
        set_my_prop(value)

func set_my_prop(value):
    my_prop = value # Infinite recursion, since `set_my_prop()` is not the setter.

Режим інструмента

За замовчуванням скрипти не виконуються всередині редактора, і можна змінювати лише експортовані властивості. У деяких випадках бажано, щоб вони виконувалися всередині редактора (якщо вони не виконують ігровий код або вручну уникають цього). Для цього існує анотація @tool, яку потрібно розмістити на початку файлу:

@tool
extends Button

func _ready():
    print("Hello")

Додаткову інформацію дивіться в Запуск коду в редакторі.

Попередження

Будьте обережні, звільняючи вузли за допомогою queue_free(), або free(), в скрипті інструмента (особливо самого власника скрипту). Оскільки скрипти інструментів виконують свій код у редакторі, неправильне використання може призвести до збою редактора.

Управління пам'яттю

Godot реалізує підрахунок посилань, щоб звільнити певні екземпляри, які більше не використовуються, замість збирача сміття або потребують суто ручного керування. Будь-який екземпляр класу class_RefCounted (або будь-який клас, який його успадковує, наприклад class_Resource) буде автоматично звільнено, коли він більше не використовується. Примірник будь-якого класу, який не є class_RefCounted (наприклад, class_Node або базовий тип class_Object), він залишатиметься в пам’яті, доки його не буде видалено з free () (або queue_free() для вузлів).

Примітка

Якщо class_Node видалено за допомогою free() або queue_free(), усі його дочірні елементи також будуть рекурсивно видалені.

Щоб уникнути посилальних циклів, які не можна звільнити, надається функція class_WeakRef для створення слабких посилань, які дозволяють отримати доступ до об’єкта, не перешкоджаючи звільненню class_RefCounted. Ось приклад:

extends Node

var my_file_ref

func _ready():
    var f = FileAccess.open("user://example_file.json", FileAccess.READ)
    my_file_ref = weakref(f)
    # the FileAccess class inherits RefCounted, so it will be freed when not in use

    # the WeakRef will not prevent f from being freed when other_node is finished
    other_node.use_file(f)

func _this_is_called_later():
    var my_file = my_file_ref.get_ref()
    if my_file:
        my_file.close()

Крім того, коли не використовуються посилання, is_instance_valid(instance) можуть бути використані для перевірки, чи об'єкт звільнений.

Сигнали

Сигнали - це інструмент для передачі повідомлень від об'єкта, на який можуть реагувати інші об'єкти. Щоб створити спеціальні сигнали для класу, використовуйте ключове слово signal.

extends Node


# A signal named health_depleted.
signal health_depleted

Примітка

Сигнали - це механізм Callback . Вони також виконують роль спостерігачів, загальна схема програмування. Для отримання додаткової інформації читайте Observer tutorial у книзі "Шаблони програмування ігор".

Ви можете підключити ці сигнали до методів так само, як підключаєте вбудовані сигнали вузлів, наприклад Button або class_RigidBody3D.

У наведеному нижче прикладі ми підключаємо сигнал health_depleted від вузла Character до вузла Game. Коли вузол Character випромінює сигнал, викликається сигнал _on_character_health_depleted ігрового вузла:

# game.gd

func _ready():
    var character_node = get_node('Character')
    character_node.health_depleted.connect(_on_character_health_depleted)


func _on_character_health_depleted():
    get_tree().reload_current_scene()

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

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

У нашому скрипті character.gd ми визначаємо сигнал health_changed та випромінюємо його за допомогою Signal.emit(), а з вузла Game, розташованого вище в нашому дереві сцен, ми підключаємо його до Lifebar за допомогою методу Signal.connect():

# character.gd

...
signal health_changed


func take_damage(amount):
    var old_health = health
    health -= amount

    # We emit the health_changed signal every time the
    # character takes damage.
    health_changed.emit(old_health, health)
...
# lifebar.gd

# Here, we define a function to use as a callback when the
# character's health_changed signal is emitted.

...
func _on_Character_health_changed(old_value, new_value):
    if old_value > new_value:
        progress_bar.modulate = Color.RED
    else:
        progress_bar.modulate = Color.GREEN

    # Imagine that `animate` is a user-defined function that animates the
    # bar filling up or emptying itself.
    progress_bar.animate(old_value, new_value)
...

У вузлі Game ми отримуємо вузли Character і Lifebar, і підключаємо вузол, який випромінює сигнал, до приймача, у цьому випадку вузол Lifebar.

# game.gd

func _ready():
    var character_node = get_node('Character')
    var lifebar_node = get_node('UserInterface/Lifebar')

    character_node.health_changed.connect(lifebar_node._on_Character_health_changed)

Це дозволяє Lifebar реагувати на зміни стану здоров’я, без з'єднання з вузлом Character.

Ви можете написати назви необов'язкових аргументів у дужках після визначення сигналу:

# Defining a signal that forwards two arguments.
signal health_changed(old_value, new_value)

These arguments show up in the editor's Signals dock, and Godot can use them to generate callback functions for you. However, you can still emit any number of arguments when you emit signals; it's up to you to emit the correct values.

../../../_images/gdscript_basics_signals_node_tab_1.png

Ви також можете створювати копії об'єктів GDScript Callable, які приймають додаткові аргументи, використовуючи Callable.bind(). Це дозволяє додавати додаткову інформацію до з'єднання, якщо сам випромінюваний сигнал не надає вам доступу до всіх необхідних даних.

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

Спираючись на наведений вище приклад, припустимо, що ми хочемо відобразити на екрані журнал пошкоджень, отриманих кожним персонажем, наприклад, Player1 отримав 22 пошкодження. Сигнал health_changed не дає нам імені персонажа, який отримав пошкодження. Тому, коли ми підключаємо сигнал до ігрової консолі, ми можемо додати ім'я персонажа за допомогою методу bind:

# game.gd

func _ready():
    var character_node = get_node('Character')
    var battle_log_node = get_node('UserInterface/BattleLog')

    character_node.health_changed.connect(battle_log_node._on_Character_health_changed.bind(character_node.name))

Наш вузол BattleLog отримує кожен зв'язаний елемент як додатковий аргумент:

# battle_log.gd

func _on_Character_health_changed(old_value, new_value, character_name):
    if not new_value <= old_value:
        return

    var damage = old_value - new_value
    label.text += character_name + " took " + str(damage) + " damage."

Сигнали очікування або співпрограми

Ключове слово await можна використовувати для створення співпрограм, які чекають, доки не буде видано сигнал, перш ніж продовжити виконання. Використання ключового слова await із сигналом або викликом функції, яка також є співпрограмою, негайно поверне керування абоненту. Коли сигнал надходить (або викликана співпрограма закінчується), вона продовжить виконання з точки, де вона була зупинена.

Наприклад, щоб зупинити виконання, доки користувач не натисне кнопку, можна зробити щось подібне:

func wait_confirmation():
    print("Prompting user")
    await $Button.button_up # Waits for the button_up signal from Button node.
    print("User confirmed")
    return true

У цьому випадку wait_confirmation стає корутиною, що означає, що викликаюча сторона також повинна її очікувати:

func request_confirmation():
    print("Will ask the user")
    var confirmed = await wait_confirmation()
    if confirmed:
        print("User confirmed")
    else:
        print("User cancelled")

Зверніть увагу, що запит значення, що повертається корутиною, без await призведе до помилки:

func wrong():
    var confirmed = wait_confirmation() # Will give an error.

Однак, якщо ви не залежите від результату, ви можете просто викликати його асинхронно, що не зупинить виконання та не зробить поточну функцію корутиною:

func okay():
    wait_confirmation()
    print("This will be printed immediately, before the user press the button.")

Якщо ви використовуєте await з виразом, який не є ні сигналом, ні корутиною, значення буде повернуто негайно, і функція не передасть керування тому, хто викликає:

func no_wait():
    var x = await get_five()
    print("This doesn't make this function a coroutine.")

func get_five():
    return 5

Це також означає, що повернення сигналу з функції, яка не є корутиною, змусить викликаючу функцію очікувати цей сигнал:

func get_signal():
    return $Button.button_up

func wait_button():
    await get_signal()
    print("Button was pressed")

Примітка

На відміну від yield у попередніх версіях Godot, ви не можете отримати об’єкт стану функції. Це зроблено для забезпечення безпеки типу. З цією безпекою типу функція не може сказати, що вона повертає int, тоді як вона фактично повертає об’єкт стану функції під час виконання.

You can store the arguments passed to the signal's parameters. If there is only one parameter, the awaited value will have the same type as the argument:

func toggled():
    var signal_args = await $Button.toggled
    assert(typeof(signal_args) == TYPE_BOOL)

If there is more than one parameter, the awaited value will be of type Array:

func request_completed():
    var signal_args = await $HTTPRequest.request_completed
    assert(typeof(signal_args) == TYPE_ARRAY)

Otherwise, the awaited value will be null:

func button_up():
    var signal_args = await $Button.button_up
    assert(signal_args == null)

Ключове слово Assert

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

# Check that 'i' is 0. If 'i' is not 0, an assertion error will occur.
assert(i == 0)

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

За бажанням, ви можете передати власне повідомлення про помилку, яке буде відображатися, якщо твердження не виконується:

assert(enemy_power < 256, "Enemy is too powerful!")