GDScript — это высокоуровневый- ориентированный императивный язык программирования с постепенной типизацией, созданный для Godot. Он использует синтаксис на основе отступов, аналогичный таким языкам, как Python . Его цель — оптимизировать и тесно интегрировать с Godot Engine, обеспечивая большую гибкость при создании и интеграции контента.

GDScript полностью независим от Python и не основан на нем.

История
Примечание
Документация по истории GDScript перемещена в Часто задаваемые вопросы.

Пример 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)


# Functions.
func some_function(param1, param2, param3):
    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#, но вы никогда раньше не использовали динамически типизированный язык, рекомендуется прочитать это руководство: doc_gdscript_efficient.

Язык

Далее следует общий обзор GDScript. Подробную информацию, например, о доступных методах для массивов или других объектов, следует искать в описаниях связанных классов.

Идентификаторы

Любая строка, которая ограничивает себя алфавитными символами (от a до z и от A до Z), цифрами (от 0 до 9) и _, квалифицируется как идентификатор. Кроме того, идентификаторы не должны начинаться с цифры. Идентификаторы чувствительны к регистру (foo отличается от FOO).

Идентификаторы также могут содержать большинство символов Юникода, являющихся частью UAX#31 . Это позволяет использовать имена идентификаторов, написанные на языках, отличных от английского. Символы Юникода, которые считаются «смешивающимися» с символами ASCII и эмодзи, не допускаются в идентификаторах.

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

Ниже приведен список ключевых слов, поддерживаемых языком. Поскольку ключевые слова являются зарезервированными словами (токенами), они не могут использоваться в качестве идентификаторов. Операторы (например, innotand или or) и имена встроенных типов, перечисленные в следующих разделах, также зарезервированы.

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

Ключевое словоОписание
ifСмотрите if/else/elif.
еlifСмотрите if/else/elif.
elsеСмотрите if/else/elif.
forСм. for.
whilеСмотрите while.
mаtchСмотрите match.
breаkВыход из выполнения текущего for или while циклов.
continuеНемедленный переход к следующей итерации for или while циклов.
passИспользуется там, где наличие инструкции требуется синтаксически, но выполнение ее кода нежелательно. Например, в пустых функциях.
returnВозвращает значение из функции.
классОпределяет класс.
clаss_nameОпределяет скрипт как глобально доступный класс с указанным именем.
extеndsОбъявляет какой класс расширяет текущий класс.
isПроверяет, расширяет ли переменная данный класс, или принадлежит ли она данному встроенному типу.
inПроверяет, находится ли значение в строке, списке, диапазоне, словаре или узле. При использовании с  for, он перебирает их вместо тестирования.
asПриводит значение к данному типу, если это возможно.
sеlfСсылается на текущий экземпляр класса.
signаlОбъявляет сигнал.
funсОбъявляет функцию.
statiсОпределяет статическую функцию или статическую переменную
сonstОбъявляет константу.
enumОбъявляет перечисление.
varОбъявляет переменную.
brеakpointПомощник редактора для точек останова отладчика. В отличие от точек останова, создаваемых щелчком мыши по желобу,  breakpoint они сохраняются в самом скрипте. Это делает его постоянным на разных машинах при использовании контроля версий.
prеloadПредварительно загружает класс или переменную. См. Классы как ресурсы.
awaitОжидает завершения сигнала или сопрограммы. Ожидание сигналов или сопрограмм .
yieldРанее использовался для сопрограмм. Сохранено как ключевое слово для перехода.
assеrtЗадает условие, регистрирует ошибку при сбое. Игнорируется в не отладочных сборках. См. Ключевое слово Assert.
voidUsed to represent that a function does not return any value.
PIКонстанта Пи.
TAUКонстанта Тау.
INFInfinity constant. Used for comparisons and as result of calculations.
NANNAN (not a number) constant. Used as impossible result from calculations.

Операторы

Далее приведен список поддерживаемых операторов и их приоритет.

ОператорОписание
( )Группировка (высший приоритет)Круглые скобки на самом деле не являются оператором, но позволяют явно указать приоритет операции.
x[index]Подписка
x.attributeСсылка на атрибут
foo()Вызов функции
await xОжидание сигналов или сопрограмм
x is NodeПроверка типаСм. также функцию is_instance_of() .
x ** yВозведение в степень Умножается x само на себя y, аналогично вызову функции pow() .Примечание. В GDScript  ** оператор является левоассоциативным . Подробное примечание смотрите после таблицы.
~xПобитовое НЕ
+x-xИдентичность/Отрицание
x * y
x / y
x % y
Умножение / Деление / Остаток от деленияОператор %дополнительно используется для строк формата .Примечание. Эти операторы ведут себя так же, как C++, что может быть неожиданным для пользователей, использующих Python, JavaScript и т. д. См. подробное примечание после таблицы.
x + y
x - y
Сложение (или конкатенация)/вычитание
x << y
x >> y
Битовый сдвиг
x & yПобитовое И
x ^ yПобитовое Исключающее ИЛИ
x | yПобитовое ИЛИ
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 и его нерекомендуемый псевдоним
x and y
x && y
Логическое И и его нерекомендуемый псевдоним
x or y
x || y
Логическое ИЛИ и его нерекомендуемый псевдоним
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,  . Если это нежелательно, используйте хотя бы один литерал с плавающей запятой (x / 2.0 ), приведение ( float(x) / y) или умножение на (x * 1.0 / y ).
  2. Оператор % доступен только для целых чисел, для чисел с плавающей запятой используйте функцию fmod() .
  3. Для отрицательных значений % оператор и fmod() использует усечение вместо округления в сторону отрицательной бесконечности. Это означает, что остаток имеет знак. Если вам нужен остаток в математическом смысле, используйте вместо него функции posmod() и fposmod() .
  4. Оператор ** левоассоциативный . Это означает, что 2 ** 2 ** 3 равно (2 ** 2) ** 3  Используйте круглые скобки, чтобы явно указать необходимый вам приоритет, например 2 ** (2 ** 3)
  5. Операторы == и != иногда позволяют сравнивать значения разных типов (например,1 == 1.0  true), но в других случаях это может вызвать ошибку во время выполнения. Если вы не уверены в типах операндов, вы можете смело использовать функцию is_same() (но учтите, что она более строга в отношении типов и ссылок). Для сравнения чисел с плавающей точкой вместо этого используйте функции is_equal_approx() и is_zero_approx() .

Литералы

ЛитералТип
45Целое число в десятичной системе счисления
0x8f51Основание 16 (шестнадцатеричное) целое число
0b101010Целое число в двоичной системе счисления
3.1458.1e-10Число с плавающей точкой (вещественное число)
"Hello",'Hi'Строки
"""Привет"""Многострочная строка
&"name"StringName
^"Node/Label"Путь узла NodePath
$NodePathСокращение для  get_node("NodePath")
%UniqueNodeСокращение от get_node(“%UniqueNode”)

Типы данных int и float могут объявляться разделёнными символом  _, для удобочитаемого вида. Допустимы следующие способы записи чисел:

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.

Аннотации

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

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

В Godot члены класса можно экспортировать. Это означает, что их значение сохраняется вместе с ресурсом (например, сценой ), к которому они привязаны. Они также будут доступны для редактирования в редакторе свойств. Экспорт осуществляется с использованием @export аннотации. Аналог [SerializeField] в юнити

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

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

Дополнительные сведения об экспорте свойств см. в статье об экспорте GDScript .

Любое константное выражение, совместимое с требуемым типом аргумента, может быть передано как значение аргумента аннотации:

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 аннотация

Значения для этих свойств не назначаются сразу при инициализации узла ( Object._init ), а вычисляются и сохраняются непосредственно перед Node._ready .
Иногда нужно сохранить ссылки на части сцены в переменной. Поскольку конфигурирование сцен гарантировано только при входе в активное дерево сцены, дочерние узлы могут быть получены только при вызове функции 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.

Продолжение линии

Строку кода в GDScript можно продолжить на следующей строке, используя обратную косую черту ( \). Добавьте один в конце строки, и код в следующей строке будет вести себя так, как будто там находится обратная косая черта. Вот пример:

var a = 1 + \
2

Строку можно продолжать несколько раз, например:

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

Встроенные типы

Встроенные типы выделяются в стеке. Они передаются как значения. Это означает, что копия создается при каждом назначении или при передаче их в качестве аргументов функциям. Исключением являются ObjectArrayDictionaryи упакованные массивы (например PackedByteArray), которые передаются по ссылке, поэтому они являются общими. Все массивы Dictionaryи некоторые объекты ( NodeResource) имеют duplicate()метод, позволяющий сделать копию.

Базовые встроенные типы

Переменная в GDScript может быть определена несколькими встроенными типами.

null

null – это пустой тип данных, который не содержит никакой информации и не может принимать другие значения.

bool

Сокращенно от “boolean”, оно может содержать только true или false.

int

Сокращенно от «целое число», оно хранит целые числа (положительные и отрицательные). Он хранится как 64-битное значение, что эквивалентно int64_tC++.

float

Сохраняет действительные числа, включая десятичные, используя значения с плавающей запятой. Он хранится как 64-битное значение, что эквивалентно doubleC++. Примечание. В настоящее время структуры данных, такие как Vector2Vector3и PackedFloat32Arrayхранят 32-битные значения одинарной точности float.

String

Последовательность символов в формате Unicode . Строковые литералы могут содержать следующие escape-последовательности:

Escape-последовательностьОзначает
\nНовая строка (перевод строки)
\tСимвол горизонтальной табуляции
\rВозврат каретки
\aОповещение (звуковой сигнал/звонок)
\bВозврат
\fРазрыв страницы
\vСимвол вертикальной табуляции
\"Двойная кавычка
\'Одиночная кавычка
\\Обратная косая черта
\uXXXXКод Unicode UTF-16 XXXX (шестнадцатеричный, без учета регистра)
\UXXXXXXКод Unicode UTF-32 XXXXXX (шестнадцатеричный, без учета регистра)

Существует два способа представления экранированного символа Юникода выше 0xFFFF:

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

GDScript также поддерживает строки формата .

StringName

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

NodePath

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

Векторные встроенные типы

Vector2

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

Vector2i

То же, что и Vector2, но компоненты являются целыми числами. Полезно для представления элементов в 2D-сетке.

Rect2

2D Прямоугольник, содержащий два поля векторов: position и size. Альтернативно содержит поле end, которое представляет собой position + size.

Vector3

3D векторный тип, содержащий поля xy и z. Может быть получен как массив.

Vector3r

То же, что и Vector3, но компоненты являются целыми числами. Может использоваться для индексации элементов в 3D-сетке.

Transform2D

Матрица 3×2, используемая для 2D-преобразований.

Plane

Тип трехмерной плоскости в нормализованной форме, которая содержит normal векторное поле и d скалярное расстояние.

Quaternion

Quaternion – это тип данных, используемый для представления трехмерного вращения. Полезно для интерполяции вращений.

AABB

Выровненная по оси ограничительная рамка (или трехмерная коробка) содержит 2 поля типа Vector: position и size. Также содержит поле end, которое является position+size.

Basis

Матрица 3×3, используемая для трехмерного вращения и масштабирования. Он содержит 3 векторных поля (xy и z), а также доступен в виде массива трехмерных векторов.

Transform3D

3D-преобразование содержит поле Basis basis и поле Vector3 origin.

Встроенные типы движка

Color

Color – тип данных цвета, содержит поля rgb, и a. Он также доступен как hs, и v для оттенка(hue)/насыщенности(saturation)/значения(value).

RID

Resource ID (RID). Серверы используют общие RID для создания ссылок на непрозрачные объекты.

Object

Базовый класс для всего, что не является встроенным типом.

Встроенные типы контейнеров

Array

Общая последовательность любых типов объектов, включая другие массивы или словари (см. ниже). Массив может изменяться динамически. Массивы индексируются, начиная с индекса 0. Начиная с Godot 2.1, индексы могут быть отрицательными, как и в Python, считая с конца.

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 4.0 добавлена ​​поддержка типизированных массивов. При операциях записи 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)

Единственное исключение было сделано для типа ArrayArray[Variant]) для удобства пользователя и совместимости со старым кодом. Однако операции с нетипизированными массивами считаются небезопасными.

Упакованные массивы

Массивы в GDScript выделяются в памяти линейно для повышения скорости работы. Однако большие массивы (более десятков тысяч элементов) могут привести к фрагментации памяти. Если это проблема, то доступны специальные типы массивов. Они принимают только один тип данных. Они избегают фрагментации памяти и также потребляют меньше памяти, но являются атомными и, как правило, работают медленнее, чем обычные массивы. Поэтому их рекомендуется использовать только для больших наборов данных:

Словарь – 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])

Примечание

Синтаксис скобок может быть использован для доступа к свойствам любого объекта Object, а не только для словарей. мейте в виду, что это вызовет ошибку скрипта при попытке индексировать несуществующее свойство. Чтобы этого избежать, вместо этого используйте методы Object.get() и Object.set().

Сигнал

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

Сигналы лучше использовать, получая их от реальных объектов, например $Button.button_up.

Callableвозможность вызова

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

Получение метода в качестве члена возвращает вызываемый объект. установит значение вызываемого объекта в качестве объекта и метода.var 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 order.

Опционально: Переменные могут иметь спецификацию типа. Когда тип указан, переменная всегда должна иметь один и тот же тип, а попытка присвоить несовместимое значение вызовет ошибку.

Типы указываются при объявлении переменной символом : (двоеточие) после имени переменной, за которым следует тип.

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, Reference, etc.).
  • Константы, если они содержат скрипт ресурса (MyScript если вы объявили const MyScript = preload("res://my_script.gd")).
  • Другие классы в том же скрипте, учитывающие область видимости (InnerClass.NestedClass если вы объявили сlass NestedClass внутри сlass InnerClass в той же области видимости).
  • Классы объявленные в скрипте с ключевым словом class_name.
  • Автозагрузки зарегистрированы как синглтоны.

Примечание

Хотя Variantэто допустимая спецификация типа, это не настоящий тип. Это всего лишь означает, что нет заданного типа, что эквивалентно отсутствию статического типа вообще. Таким образом, вывод не разрешен по умолчанию для Variant, поскольку это, скорее всего, ошибка.

Вы можете отключить эту проверку или сделать ее только предупреждением, изменив ее в настройках проекта. Подробности см. в разделе Система предупреждений GDScript .

Статические переменные

Переменная-член класса может быть объявлена ​​статической:

static var a

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

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

Примечание

Аннотации @exportи @onreadyнельзя применять к статической переменной. Локальные переменные не могут быть статическими.

В следующем примере определяется Personкласс со статической переменной с именем max_id. Мы увеличиваем max_idin _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

@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

Если вы передадите имя перечислению, оно поместит все ключи в словарь констант с этим именем. Это означает, что все константные методы словаря также можно использовать с именованным перечислением.

Важно

Ключи в именованном перечислении не регистрируются как глобальные константы. Доступ к ним должен начинаться с префикса имени перечисления ( 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}

func _ready():
    # Access values with Name.KEY, prints '5'
    print(State.STATE_JUMP)
    # Use constant dictionary functions
    # prints '["STATE_IDLE", "STATE_JUMP", "STATE_SHOOT"]'
    print(State.keys())

Функции

Функции всегда принадлежат классу ` <Classes>`_ . Приоритет области видимости для поиска переменной следующий: локальная → член класса → глобальная. Переменная self всегда доступна и предоставляется как опция для доступа к членам класса, но не всегда требуется (и не должна не передаваться в качестве первого аргумента функции, в отличие от Python).

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

Функция может return (возвращать) значения. Если вы ничего не возвращаете, то она является null.

Если функция содержит только одну строку кода, ее можно записать в одну строку:

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.

Примечание

Не пустые (Non-void) функции должны всегда возвращать значение, поэтому если в вашем коде есть ветвистые выражения (такие как конструкция 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)
lambda.call(42) # Prints "42"

Лямбда-функциям можно присвоить имена в целях отладки:

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

Лямбда-функции фиксируют локальную среду. Локальные переменные передаются по значению, поэтому они не будут обновляться в лямбде, если они будут изменены в локальной функции:

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

Примечание

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

Статические функции

Функция может быть объявлена ​​статической. Когда функция является статической, она не имеет доступа к переменным-членам экземпляра или self. Статическая функция имеет доступ к статическим переменным. Также статические функции полезны для создания библиотек вспомогательных функций:

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

Лямбда-выражения не могут быть объявлены статическими.

См. также Статические переменные и Статический конструктор .

Операторы и контроль потока

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

Выражения

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

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

Вот несколько примеров выражений:

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.png") # Preload builtin function.
self # Reference to current instance.

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

if/elsе/elif

Простые условия создаются с помощью такого синтаксиса 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 могут быть вложенными для обработки более чем двух случаев. При вложении тернарных выражений 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!")

whilе

Простые циклы создаются с использованием 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 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 in 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 предлагает некоторые дополнительные возможности.

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

match (expression):
    [pattern](s):
        [block]
    [pattern](s):
        [block]
    [pattern](s):
        [block]

Предупреждение

match является более строгим по типу, чем  == оператор. Например, 1  и 1.0 не будут соответствовать . Единственным исключением является сопоставление : например, String "hello"  соответствует StringName &"hello".

Ускоренный курс для людей, знакомых с правилами switch:

  1. Заменители . switch_match
  2. Уберите case.
  3. Удалите все breaks.
  4. Смените default на единичное подчеркивание.

Управление потоком:

Узоры совмещаются сверху вниз. Если шаблон совпадает, будет выполнен первый соответствующий блок. После этого выполнение продолжается ниже matchоператора.

Примечание

Особое continue поведение, match поддерживаемое в версии 3.x, было удалено в Godot 4.0.

Существует 6 типов шаблонов:

1. Постоянный шаблон
Постоянные (константные) примитивы, такие как числа и строки

match x:
    1:
        print("We are number one!")
    2:
        print("Two are better than one!")
    "test":
        print("Oh snap! It's a string!")

2.Шаблон переменной
Соответствует содержимому переменной/перечисления(enum):

match typeof(x):
    TYPE_FLOAT:
        print("float")
    TYPE_STRING:
        print("text")
    TYPE_ARRAY:
        print("array")

3.Шаблон подстановки
Этот шаблон подходит для любого поиска. Это пишется как единичное подчёркивание.
Он может быть использован как эквивалент выражения 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.")

4. Шаблон привязки
Шаблон привязки вводит новую переменную. Как и шаблон подстановки, он соответствует всему, а также присваивает этому значению имя. Это особенно полезно в шаблонах массивов и словарей:

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)

5. Шаблон массива
Соответствует массиву. Каждый отдельный элемент шаблона массива сам по себе является шаблоном, поэтому вы можете их вкладывать.
Сначала проверяется длина массива, она должна быть того же размера, что и шаблон, в противном случае шаблон не совпадет.
Открытый массив: Массив может быть больше, чем массив, создавая последнюю подшаблонку ...

Каждый подшаблон должен быть разделен запятыми.

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

6. Шаблон словаря
Работает так же, как шаблон массива. Каждый ключ должен быть постоянным шаблоном.
Сначала проверяется размер словаря, он должен быть того же размера, что и шаблон, в противном случае шаблон не совпадет.
Открытый словарь: Словарь может быть больше шаблона, если сделать последний подшаблоном
– ...
Каждый подшаблон должен быть разделен запятыми.
Если вы не указываете значение, то проверяется только наличие ключа.
Шаблон значения отделяется от шаблона ключа символом :.

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

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

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

Классы

По умолчанию, все файлы скриптов это классы без имен. В этом случае вы можете ссылаться на них только по пути файла, с помощью относительного или абсолютного пути. Например, если вы назовете файл скрипта 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

Вот пример файла класса:

# 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

Примечание

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

Наследование

Класс (хранимый как файл) может наследоваться от:

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

Множественное наследование невозможно.

При наследовании использует ключевое слово 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

Примечание

Если наследование не определено явно, класс по умолчанию будет наследовать 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 ) также должен определить и передать соответствующие параметры в from ._init_initstate.gd
  2. idle.gd может иметь другое количество аргументов, чем базовый класс state.gd.
  3. В приведенном выше примере eконструктору передается state.gd то же самое, e что и idle.gd.
  4. Если idle.gd конструктор _init принимает 0 аргументов, ему все равно необходимо передать какое-то значение базовому 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()

Классы как ресурсы

Классы, хранящиеся в файлах, рассматриваются как ресурсы. Они должны быть загружены с диска для доступа к ним в других классах. Это делается с помощью функций 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

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

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

Эта переменная-член поддержки не создается, если вы ее не используете.

Примечание

В отличие set get от предыдущих версий Godot, методы установки и получения свойств вызываются всегда , даже если к ним обращаются внутри одного и того же класса (с префиксом или без него self.). Это делает поведение последовательным. Если вам нужен прямой доступ к значению, используйте другую переменную для прямого доступа и заставьте код свойства использовать это имя.

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

var my_prop:
    get = get_my_prop, set = set_my_prop

Это также можно сделать в той же строке.

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

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

@tool
extends Button

func _ready():
    print("Hello")

Запуск кода в редакторе.

Предупреждение

Будьте осторожны при освобождении узлов с помощью queue_free() или free() в скрипт инструмента (особенно самого владельца скрипта). Так как скрипт инструмента запускает свой код в редакторе, неправильное их использование может привести к крашу редактора.

Управление памятью

Godot реализует подсчет ссылок для освобождения определенных экземпляров, которые больше не используются вместо сборщика мусора или требуют чисто ручного управления. Любой экземпляр класса RefCounted (или любого класса, который его наследует, например Resource ), будет автоматически освобожден, когда он больше не будет использоваться. Экземпляр любого класса, который не является RefCounted (например, Node или базовый тип объекта ), будет оставаться в памяти до тех пор, пока не будет удален с помощью free()(или queue_free() для узлов).

Примечание

Если узел удаляется с помощью free()или queue_free(), все его дочерние элементы также будут рекурсивно удалены.

Чтобы избежать ссылочных циклов, которые невозможно освободить, предусмотрена функция WeakRef для создания слабых ссылок, которые позволяют получить доступ к объекту, не препятствуя освобождению 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 механизм. Они также выполняют роль наблюдателя, распространенный шаблон программирования. Для лучшего понимания информации, читайте про шаблон наблюдатель в словаре Шаблонов Игрового Программирования.

Вы можете подключить эти сигналы к методам так же, как вы подключаете встроенные сигналы узлов, таких как Button или 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)

Эти аргументы показываются в доке узла редактора, и Godot может использовать их, чтобы производить для вас функции обратного вызова. Однако, вы всё ещё можете отправлять любое число аргументов при отправке сигналов; отправка правильных значений (и их проверок) зависит только от вас.

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

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

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

Основываясь на предыдущем примере, представим, что мы хотим показать лог урона, полученного всеми персонажами на экране, вроде Игрок1 получил 22 единицы урона. Сигнал health_changed не выдаёт имя получившего урон персонажа. Так что, когда мы соединим сигнал с внутриигровой консолью, мы можем добавить имя персонажа в связку аргумента массива:

# 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, [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 хотя на самом деле она возвращает объект состояния функции во время выполнения.

Ключевое слово 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!")

Источник:
https://docs.godotengine.org/ru/4.x/tutorials/scripting/gdscript/gdscript_basics.html