GDScript basics

Введение

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

История

В ранние годы в движке использовался скриптовый язык Lua. Lua работает быстро, но привязка к объектно-ориентированной системе (с помощью откатов) была сложной и медленной и занимало огромное количество кода. После некоторых экспериментов с Python, это оказалось также трудно внедрить.

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

  • Godot встраивает скрипты в узлы. Большинство языков не предназначены для этого.
  • Godot использует несколько встроенных типов данных для 2D и 3D математики. Скриптовые языки не предоставляют такого функционала, поэтому привязывать их неэффективно.
  • Godot очень активно использует потоки для считывания и инициализации данных из сети или с диска. Скриптовые интерпретаторы популярных языков не очень хорошо дружат с этим.
  • Godot уже имеет модель управления памятью для ресурсов, а большинство скриптовых языков предоставляют свою собственную модель, что приводит к двойной работе и множеству багов.
  • Код привязки всегда неряшливый и приводит к некоторым ошибкам, неожиданным багам и, как правило, низкой поддержке.

Результатом этих рассуждений является GDScript. Язык и интерпретатор для GDScript оказался меньше чем код привязки для Lua и Squirrel, имея при этом такую же функциональность. Со временем наличие встроенного языка оказалось огромным преимуществом.

Пример GDScript

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

# A file is a class!

# Inheritance

extends BaseClass

# (optional) class definition with a custom icon

class_name MyClass, "res://path/to/optional/icon.svg"

# Member Variables

var a = 5
var s = "Hello"
var arr = [1, 2, 3]
var dict = {"key": "value", 2:3}
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

func some_function(param1, param2):
    var local_var = 5

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

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

    while param2 != 0:
        param2 -= 1

    var local_var2 = param1 + 3
    return local_var2

# Functions override functions with the same name on the base/parent class.
# If you still want to call them, use '.' (like 'super' in other languages).

func something(p1, p2):
    .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).

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

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

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

Ключевое слово Описание
if Смотрите if/else/elif.
elif Смотрите if/else/elif.
else Смотрите if/else/elif.
for См. for.
while Смотрите while.
match Смотрите match.
break Выход из выполнения текущего for или while циклов.
continue Немедленный переход к следующей итерации for или while циклов.
pass Используется там, где наличие инструкции требуется синтаксически, но выполнение ее кода нежелательно. Например, в пустых функциях.
return Возвращает значение из функции.
class Объявляет класс.
extends Объявляет какой класс расширяет текущий класс.
is Tests whether a variable extends a given class, or is of a given built-in type.
as Cast the value to a given type if possible.
self Ссылается на текущий экземпляр класса.
tool Выполняет скрипт в редакторе.
signal Объявляет сигнал.
func Объявляет функцию.
static Объявление статической функции. Статические поля класса не доступны.
const Объявляет константу.
enum Объявляет перечисление.
var Объявляет переменную.
onready Инициализирует переменную, как только Узел, к которому прикреплен скрипт, а также его дети являются частью дерева сцен.
export Сохраняет переменную вместе с ресурсом, к которому она привязана, и делает ее видимой и модифицируемой в редакторе.
setget Определяет функции setter и getter для переменной.
breakpoint Помощник редактора для контрольных точек отладчика.
preload Предварительно загружает класс или переменную. См. Классы как ресурсы.
yield Поддержка сопрограмм. См. Сопрограммы с yield.
assert Задает условие, регистрирует ошибку при сбое. Игнорируется в не отладочных сборках. См. Ключевое слово Assert.
remote Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.
master Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.
puppet Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.
remotesync Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.
mastersync Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.
puppetsync Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.
PI Константа Пи.
TAU Константа Тау.
INF Бесконечность. Используется для сравнений.
NAN NAN (англ. Not-a-Number, «не число»). Используется для сравнений.

Операторы

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

Оператор Описание
x[index] Подпись, Высокий Приоритет
x.attribute Ссылка на атрибут
is Проверка типа экземпляра
~ Побитовое НЕ
-x Отрицательный / Унарный Отрицательный
* / %

Умножение / Деление / Остаток от деления

Эти операторы ведут себя так же, как и в C++. Целочисленное деление усекается, а не возвращает дробное число, и оператор % доступен только для значений типа int(«fmod» для типа float)

+ Прибавление / Конкатенация массивов
- Вычитание
<< >> Битовый Сдвиг
& Побитовое И
^ Побитовое Исключающее ИЛИ
| Побитовое ИЛИ
< > == != >= <= Сравнения
in Тестирование контента
! not Логическое НЕТ
and && Логическое И
or || Логическое ИЛИ
if x else Тернарный оператор Если/Иначе (if/else)
= += -= *= /= %= &= |= Присваивание, Низкий Приоритет

Литералы

Литерал Тип
45 Целое число в десятичной системе счисления
0x8F51 Целое число в шестнадцатеричной системе счисления
3.14, 58.1e-10 Число с плавающей точкой (вещественное число)
"Привет", "Дарова!" Строки
"""Приииивет""" Многострочная строка
@"Node/Label" NodePath или StringName
$NodePath Сокращение для get_node("NodePath")

Комментарии

Все, что находится после символа # до конца строки игнорируется и считается комментарием.

# This is a comment.

Многострочные комментарии могут быть созданы с использованием «»«(три кавычки подряд) в начале и конце блока текста. Обратите внимание, что это создает строку, поэтому она не будет удалена при компиляции скрипта.

""" Everything on these
lines is considered
a comment. """

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

Встроенные типы распределены по стекам. Они передаются как значения. Это означает, что копия создается на каждом присваивании или при передаче их в качестве аргументов функциям. Единственным исключением являются Array и Dictionaries, которые передаются по ссылке и являются общими. (Не PoolArray типа PoolByteArray, но они тоже передаются как значения, поэтому учитывайте это при решении, что именно нужно использовать для Вашего проекта!)

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

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

null

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

bool

Булевый тип данных может содержать только true или false.

int

Целочисленный тип данных может содержать только целые числа (как отрицательные, так и положительные).

float

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

String

Последовательность символов в формате Unicode. Строки могут содержать стандартные escape-последовательности C. GDScript поддерживает: ref:форматирование строки, то есть функция printf <doc_gdscript_printf>.

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

Vector2

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

Rect2

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

Vector3

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

Transform2D

Матрица 3x2, используемая для двумерных преобразований.

Plane

3D Plane type in normalized form that contains a normal vector field and a d scalar distance.

Quat

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

AABB

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

Basis

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

Transform

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

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

Color

Color - тип данных цвета, содержит поля r, g, b, и a. Он также доступен как h, s, и v для оттенка(hue)/насыщенности(saturation)/значения(value).

NodePath

Составной путь к узлу, используемый в основном в системе сцен. Он может быть легко переведен на строку и из нее.

RID

Resource ID (RID). Servers use generic RIDs to reference opaque data.

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

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

  • PoolByteArray: Массив байтов (целые числа от 0 до 255).
  • PoolIntArray: Массив целых чисел.
  • PoolRealArray: Массив чисел с плавающей точкой.
  • PoolStringArray: Массив строк.
  • PoolVector2Array: Массив объектов типа Vector2.
  • PoolVector3Array: Массив объектов типа Vector3.
  • PoolColorArray: Массив объектов типа 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 и при желании, есть возможность присвоить значение при инициализации.

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 = Sprite.new()

Если переменная инициализирована в объявлении, то можно предугадать типе сразу, поэтому можно не писать имя типа:

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

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

Поддерживаемые типы:

  • Встроенные типы (Array, Vector2, int, String и т.д.)
  • Классы движка (Node, Resource, Reference, etc.)
  • Константы, если они содержат скрипт ресурса (MyScript если вы объявили const MyScript = preload("res://my_script.gd")).
  • Другие классы в том же скрипте, учитывающие область видимости (InnerClass.NestedClass если вы объявили сlass NestedClass внутри сlass InnerClass в той же области видимости)
  • Классы объявленные в скрипте с ключевым словом class_name.

Приведение переменных

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

Casting between object types results in the same object if the value is of the same type or a subtype of the cast type.

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

Если значение не является подтипом, операция приведения приведет к получению значения null.

var my_node2D: Node2D
my_node2D = $Button # 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

Casting is also useful to have better type-safe variables when interacting with tree:

# will infer the variable to be of type Sprite:
var my_sprite := $Character as Sprite

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

Константы

Константы аналогичны переменным, но должны быть постоянными или постоянными выражениями и присваиваются при инициализации.

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

Хотя тип констант берется из присвоенного значения, можно также явно описать тип:

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

Присвоение значения несовместимого типа приведет к ошибке.

Перечисления

Перечисления это фактически сокращения для констант, и они очень полезны если вы хотите присвоить конкретные числовые значения некоторым константам.

If you pass a name to the enum, it will put all the keys inside a constant dictionary of that name.

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

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.

Функции

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

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

Примечание

Не пустые (Non-void) функции должны всегда возвращать значение, поэтому если в вашем коде есть ветвистые выражения (такие как конструкция if/else), все возможные пути должны иметь ответвление. Например, если return внутри блока if, но не после него, то редактор выдаст ошибку, потому что если условие не будет выполнено, то функция не будет иметь действительного значения для возврата.

Ссылочные функции

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

Для обращения к функции по имени во время выполнения (например, для хранения ее в переменной или передачи другой функции в качестве аргумента) необходимо использовать помощники call или funcref:

# Call a function by name in one step.
my_node.call("my_function", args)

# Store a function reference.
var my_func = funcref(my_node, "my_function")
# Call stored function reference.
my_func.call_func(args)

Remember that default functions, like _init, and most notifications, such as _enter_tree, _exit_tree, _process, _physics_process, etc. are called in all base classes automatically. So there is only a need to call the function explicitly when overloading them in some way.

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

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

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

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

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

if/else/elif

Простые условия создаются с помощью такого синтаксиса if/else/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

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 dict = {"a": 0, "b": 1, "c": 2}
for i in dict:
    print(dict[i])

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 c in "Hello":
    print(c) # Iterate through all characters in a String, print every letter on new line.

match

Оператор match используется для ветвления. Это эквивалентно оператору switch, встречающемуся на многих других языках, но match предлагает некоторые дополнительные возможности.

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

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

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

  1. Замените switch на match
  2. Уберите case
  3. Remove any breaks. If you don’t want to break by default, you can use continue for a fallthrough.
  4. Смените default на единичное подчеркивание.

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

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

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

  • постоянный (константный) шаблон

    постоянные (константные) примитивы, такие как числа и строки

    match x:
        1:
            print("We are number one!")
        2:
            print("Two are better than one!")
        "test":
            print("Oh snap! It's a string!")
    
  • переменный шаблон

    matches the contents of a variable/enum

    match typeof(x):
        TYPE_REAL:
            print("float")
        TYPE_STRING:
            print("text")
        TYPE_ARRAY:
            print("array")
    
  • шаблон подстановки

    This pattern matches everything. It’s written as a single underscore.

    Он может быть использован как эквивалент выражения 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 tbh.")
    
  • шаблон привязки

    A binding pattern introduces a new variable. Like the wildcard pattern, it matches everything - and also gives that value a name. It’s especially useful in array and dictionary patterns.

    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)
    
  • шаблон массива

    matches an array. Every single element of the array pattern is a pattern itself, so you can nest them.

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

    Open-ended array: An array can be bigger than the pattern by making the last subpattern ..

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

    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")
    
  • dictionary pattern

    Работает так же, как и шаблон массива. Каждый ключ должен быть постоянным шаблоном (constant pattern).

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

    Открытый словарь: Словарь может быть больше шаблона, если сделать последний подшаблоном - ..

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

    Если вы не указываете значение, то проверяется только наличие ключа.

    Шаблон значения отделяется от шаблона ключа символом :

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

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

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“. Также Вы можете, добавить запятую с указанием пути к изображению, чтобы использовать ее в качестве иконки. Ваш класс появится с этим значком в редакторе:

# Item.gd

extends Node

class_name Item, "res://interface/icons/item.png"
../../../_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)

Примечание

Godot’s class syntax is compact: it can only contain member variables or functions. You can use static functions, but not static member variables. In the same way, the engine initializes variables every time you create an instance, and this includes arrays and dictionaries. This is in the spirit of thread safety, since scripts can be initialized in separate threads without the user knowing.

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

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

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

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

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

To check if a given instance inherits from a given class, the is keyword can be used:

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

# [...]

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

Для вызова функции в базовом классе (т.е. функции extend (расширения) в текущем классе), добавьте . к имени функции:

.basefunc(args)

Это особенно полезно, так как функции при расширении классов заменяют одноименные функции в своих базовых классах. Так что если вы все еще хотите обратится к ним, вы можете использовать . как ключевое слово super в других языках:

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

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

Конструктор класса вызывается при создании экземпляра класса (инстанциировании) и называется _init. Как говорилось ранее, конструктор класса-родителя вызывается автоматически, когда происходит наследование. Таким образом, необходимости вызывать ._init вручную обычно нет.

Unlike the call of a regular function, like in the above example with .some_func, if the constructor from the inherited class takes arguments, they are passed like this:

func _init(args).(parent_args):
   pass

Это лучше объяснить на примере. Допустим у нас следующая ситуация:

# 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).(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. if Idle.gd’s _init constructor takes 0 arguments, it still needs to pass some value to the State.gd base class even if it does nothing. Which brings us to the fact that you can pass literals in the base constructor as well, not just variables. Eg.:
# Idle.gd

func _init().(5):
    pass

Внутренние классы

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

Classes as resources

Классы, хранящиеся в файлах, рассматриваются как ресурсы. Они должны быть загружены с диска для доступа к ним в других классах. Это делается с помощью функций load или preload (см. ниже). Создание экземпляра загруженного ресурса класса осуществляется вызовом функции new на объекте класса:

# Load the class resource when calling load().
var my_class = 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()

Экспорт

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

extends Button

export var number = 5 # Value will be saved and visible in the property editor.

An exported variable must be initialized to a constant expression or have an export hint in the form of an argument to the export keyword (see below).

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

# If the exported value assigns a constant or constant expression,
# the type will be inferred and used in the editor.

export var number = 5

# Export can take a basic data type as an argument, which will be
# used in the editor.

export(int) var number

# Export can also take a resource type to use as a hint.

export(Texture) var character_face
export(PackedScene) var scene_file
# There are many resource types that can be used this way, try e.g.
# the following to list them:
export(Resource) var resource

# Integers and strings hint enumerated values.

# Editor will enumerate as 0, 1 and 2.
export(int, "Warrior", "Magician", "Thief") var character_class
# Editor will enumerate with string names.
export(String, "Rebecca", "Mary", "Leah") var character_name

# Named Enum Values

# Editor will enumerate as THING_1, THING_2, ANOTHER_THING.
enum NamedEnum {THING_1, THING_2, ANOTHER_THING = -1}
export (NamedEnum) var x

# Strings as Paths

# String is a path to a file.
export(String, FILE) var f
# String is a path to a directory.
export(String, DIR) var f
# String is a path to a file, custom filter provided as hint.
export(String, FILE, "*.txt") var f

# Using paths in the global filesystem is also possible,
# but only in tool scripts (see further below).

# String is a path to a PNG file in the global filesystem.
export(String, FILE, GLOBAL, "*.png") var tool_image
# String is a path to a directory in the global filesystem.
export(String, DIR, GLOBAL) var tool_dir

# The MULTILINE setting tells the editor to show a large input
# field for editing over multiple lines.
export(String, MULTILINE) var text

# Limiting editor input ranges

# Allow integer values from 0 to 20.
export(int, 20) var i
# Allow integer values from -10 to 20.
export(int, -10, 20) var j
# Allow floats from -10 to 20, with a step of 0.2.
export(float, -10, 20, 0.2) var k
# Allow values y = exp(x) where y varies between 100 and 1000
# while snapping to steps of 20. The editor will present a
# slider for easily editing the value.
export(float, EXP, 100, 1000, 20) var l

# Floats with Easing Hint

# Display a visual representation of the ease() function
# when editing.
export(float, EASE) var transition_speed

# Colors

# Color given as Red-Green-Blue value
export(Color, RGB) var col # Color is RGB.
# Color given as Red-Green-Blue-Alpha value
export(Color, RGBA) var col # Color is RGBA.

# Another node in the scene can be exported, too.

export(NodePath) var node

Следует отметить, что даже если во время работы в редакторе скрипт не выполняется, экспортируемые свойства все равно можно редактировать (см. ниже слово «tool»).

Экспорт битовых флагов

Целые числа, используемые в качестве битовых флагов, могут хранить несколько true/false (булевых) значений в одном свойстве. Используя подсказку экспорта int, FLAGS, их можно настроить в редакторе:

# Individually edit the bits of an integer.
export(int, FLAGS) var spell_elements = ELEMENT_WIND | ELEMENT_WATER

Restricting the flags to a certain number of named flags is also possible. The syntax is similar to the enumeration syntax:

# Set any of the given flags from the editor.
export(int, FLAGS, "Fire", "Water", "Earth", "Wind") var spell_elements = 0

В этом примере у Fire значение 1, у Water - 2, у Earth - 4 и у Wind - 8. Обычно константы должны быть определены соответствующим образом (например, const ELEMENT_WIND = 8 и так далее).

Using bit flags requires some understanding of bitwise operations. If in doubt, boolean variables should be exported instead.

Экспорт массивов

Exporting arrays works, but with an important caveat: While regular arrays are created local to every class instance, exported arrays are shared between all instances. This means that editing them in one instance will cause them to change in all other instances. Exported arrays can have initializers, but they must be constant expressions.

# Exported array, shared between all instances.
# Default value must be a constant expression.

export var a = [1, 2, 3]

# Exported arrays can specify type (using the same hints as before).

export(Array, int) var ints = [1,2,3]
export(Array, int, "Red", "Green", "Blue") var enums = [2, 1, 0]
export(Array, Array, float) var two_dimensional = [[1.0, 2.0], [3.0, 4.0]]

# You can omit the default value, but then it would be null if not assigned.

export(Array) var b
export(Array, PackedScene) var scenes

# Typed arrays also work, only initialized empty:

export var vector3s = PoolVector3Array()
export var strings = PoolStringArray()

# Regular array, created local for every instance.
# Default value can include run-time values, but can't
# be exported.

var c = [a, 2, 3]

Сеттеры/геттеры

Часто полезно знать, когда переменная, входящая в состав класса, изменяется по какой-то причине. Возможно вы хотите ее инкапсулировать.

Для этого GDScript предоставляет синтаксис setter/getter, используя ключевое слово setget. Использовать непосредственно после определения переменной:

var variable = value setget setterfunc, getterfunc

Всякий раз, когда значение variable изменяется внешним источником (т.е. не от локального использования в классе), вызывается функция setter (setterfunc выше). Это происходит до того, как значение изменится. Сеттер должен решить, что делать с новым значением. И наоборот, когда функция variable доступна, функция getter (getterfunc выше) должна return(возвращать) желаемое значение. Ниже приведен пример:

var myvar setget my_var_set, my_var_get

func my_var_set(new_value):
    my_var = new_value

func my_var_get():
    return my_var # Getter must return a value.

Either of the setter or getter functions can be omitted:

# Only a setter.
var my_var = 5 setget myvar_set
# Only a getter (note the comma).
var my_var = 5 setget ,myvar_get

Get/Setters are especially useful when exporting variables to editor in tool scripts or plugins, for validating input.

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

func _init():
    # Does not trigger setter/getter.
    my_integer = 5
    print(my_integer)

    # Does trigger setter/getter.
    self.my_integer = 5
    print(self.my_integer)

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

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

tool
extends Button

func _ready():
    print("Hello")

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

Be cautious when freeing nodes with queue_free() or free() in a tool script (especially the script’s owner itself). As tool scripts run their code in the editor, misusing them may lead to crashing the editor.

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

If a class inherits from class_Reference, then instances will be freed when no longer in use. No garbage collector exists, just reference counting. By default, all classes that don’t define inheritance extend Reference. If this is not desired, then a class must inherit class_Object manually and must call instance.free(). To avoid reference cycles that can’t be freed, a weakref function is provided for creating weak references.

Alternatively, when not using references, the is_instance_valid(instance) can be used to check if an object has been freed.

Сигналы

Signals are a tool to emit messages from an object that other objects can react to. To create custom signals for a class, use the signal keyword.

extends Node

# A signal named health_depleted
signal health_depleted

Примечание

Signals are a Callback mechanism. They also fill the role of Observers, a common programming pattern. For more information, read the Observer tutorial in the Game Programming Patterns ebook.

You can connect these signals to methods the same way you connect built-in signals of nodes like class_Button or class_RigidBody.

In the example below, we connect the health_depleted signal from a Character node to a Game node. When the Character node emits the signal, the game node’s _on_Character_health_depleted is called:

# Game.gd

func _ready():
   var character_node = get_node('Character')
   character_node.connect("health_depleted", self, "_on_Character_health_depleted")

func _on_Character_health_depleted():
   get_tree().reload_current_scene()

You can emit as many arguments as you want along with a signal.

Here is an example where this is useful. Let’s say we want a life bar on screen to react to health changes with an animation, but we want to keep the user interface separate from the player in our scene tree.

In our Character.gd script, we define a health_changed signal and emit it with Object.emit_signal(), and from a Game node higher up our scene tree, we connect it to the Lifebar using the Object.connect() method:

# 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
    emit_signal("health_changed", 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)
...

Примечание

To use signals, your class has to extend the Object class or any type extending it like Node, KinematicBody, Control

In the Game node, we get both the Character and Lifebar nodes, then connect the character, that emits the signal, to the receiver, the Lifebar node in this case.

# Game.gd

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

   character_node.connect("health_changed", lifebar_node, "_on_Character_health_changed")

This allows the Lifebar to react to health changes without coupling it to the Character node.

you can write optional argument names in parentheses after the signal’s definition.

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

These arguments show up in the editor’s node 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. So it’s up to you to emit the correct values.

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

GDScript can bind an array of values to connections between a signal and a method. When the signal is emitted, the callback method receives the bound values. These bound arguments are unique to each connection, and the values will stay the same.

You can use this array of values to add extra constant information to the connection if the emitted signal itself doesn’t give you access to all the data that you need.

Building on the example above, let’s say we want to display a log of the damage taken by each character on the screen, like Player1 took 22 damage.. The health_changed signal doesn’t give us the name of the character that took damage. So when we connect the signal to the in-game console, we can add the character’s name in the binds array argument:

# Game.gd

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

   character_node.connect("health_changed", battle_log_node, "_on_Character_health_changed", [character_node.name])

Our BattleLog node receives each element in the binds array as an extra argument:

# BattleLog.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."

Coroutines with yield

GDScript offers support for coroutines via the yield built-in function. Calling yield() will immediately return from the current function, with the current frozen state of the same function as the return value. Calling resume on this resulting object will continue execution and return whatever the function returns. Once resumed, the state object becomes invalid. Here is an example:

func my_func():
   print("Hello")
   yield()
   print("world")

func _ready():
    var y = my_func()
    # Function state saved in 'y'.
    print("my dear")
    y.resume()
    # 'y' resumed and is now an invalid state.

Выведет:

Hello
my dear
world

Также можно передавать значения между функциями yield() и resume(), например:

func my_func():
   print("Hello")
   print(yield())
   return "cheers!"

func _ready():
    var y = my_func()
    # Function state saved in 'y'.
    print(y.resume("world"))
    # 'y' resumed and is now an invalid state.

Выведет:

Hello
world
cheers!

Корутины и сигналы

Реальная сила использования yield заключается в их сочетании с сигналами. yield может принимать два аргумента, объект и сигнал. Когда сигнал будет получен, выполнение будет возобновлено. Вот несколько примеров:

# Resume execution the next frame.
yield(get_tree(), "idle_frame")

# Resume execution when animation is done playing.
yield(get_node("AnimationPlayer"), "finished")

# Wait 5 seconds, then resume execution.
yield(get_tree().create_timer(5.0), "timeout")

Сами корутины используют сигнал completed при переходе в недействительное состояние, например:

func my_func():
    yield(button_func(), "completed")
    print("All buttons were pressed, hurray!")

func button_func():
    yield($Button0, "pressed")
    yield($Button1, "pressed")

my_func будет продолжаться только после нажатия обеих кнопок.

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

When using nodes, it’s common to desire to keep references to parts of the scene in a variable. As scenes are only warranted to be configured when entering the active scene tree, the sub-nodes can only be obtained when a call to Node._ready() is made.

var my_label

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

Это может стать немного громоздким, особенно когда узлы и внешние ссылки накапливаются. Для этого в GDScript есть ключевое слово onready, которое откладывает инициализацию входящей переменной до вызова _ready. Он может заменить приведенный выше код одной строкой:

onready var my_label = get_node("MyLabel")

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

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

# Check that 'i' is 0.
assert(i == 0)