Основы GDScript

Введение

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

История

Примечание

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

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

еlif

Смотрите if/else/elif.

elsе

Смотрите if/else/elif.

for

См. for.

whilе

Смотрите while.

mаtch

Смотрите match.

breаk

Выход из выполнения текущего for или while циклов.

continuе

Немедленный переход к следующей итерации for или while циклов.

пропуск

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

return

Возвращает значение из функции.

класс

Определяет внутренний класс.

clаss_name

Привязка названия класса и необязательной иконки для вашего скрипта.

extеnds

Объявляет какой класс расширяет текущий класс.

is

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

as

Приводит значение к данному типу, если это возможно.

sеlf

Ссылается на текущий экземпляр класса.

toоl

Выполняет скрипт в редакторе.

signаl

Объявляет сигнал.

funс

Объявляет функцию.

statiс

Объявление статической функции. Статические поля класса не доступны.

сonst

Объявляет константу.

enum

Объявляет перечисление.

var

Объявляет переменную.

оnready

Инициализирует переменную, как только Узел, к которому прикреплен скрипт, а также его дети являются частью дерева сцен.

export

Сохраняет переменную вместе с ресурсом, к которому она привязана, и делает ее видимой и модифицируемой в редакторе.

sеtget

Определяет функции setter и getter для переменной.

brеakpoint

Помощник редактора для контрольных точек отладчика.

prеload

Предварительно загружает класс или переменную. См. Классы как ресурсы.

выход

Поддержка сопрограмм. См. Сопрограммы с промежуточным возвратом.

assеrt

Задает условие, регистрирует ошибку при сбое. Игнорируется в не отладочных сборках. См. Ключевое слово Assert.

rеmote

Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.

master

Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.

кукла

Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.

remotеsync

Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.

mastersуnc

Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.

puppetsуnc

Сетевая аннотация RPC. См. документацию по многопользовательскому режиму высокого уровня.

PI

Константа Пи.

TAU

Константа Тау.

INF

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

NAN

NAN (англ. Not-a-Number, "не число"). Используется для сравнений.

Операторы

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

Оператор

Описание

x[index]

Подписка (высший приоритет)

x.attribute

Ссылка на атрибут

foo()

Вызов функции

is

Проверка типа экземпляра

~

Побитовое НЕ

-x

Отрицательное / унарное отрицание

* / %

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

Эти операторы ведут себя так же, как C++. Целочисленное деление усекается, а не возвращает дробное число, а оператор % доступен только для целых чисел ("fmod" для чисел с плавающей запятой) и дополнительно используется для Format Strings

+

Добавление / объединение массивов

-

Вычитание

<< >>

Битовый сдвиг

&

Побитовое И

^

Побитовое Исключающее ИЛИ

|

Побитовое ИЛИ

< > == != >= <=

Сравнения

in

When used with the if keyword it checks if a value is within a string, list, range, dictionary, or node. When used with the for keyword it is used to iterate though the contents of a string, list, range, dictionary or node.

! not

Логическое НЕ

and &&

Логическое И

or ||

Логическое ИЛИ

if x else

Тернарный оператор Если/Иначе (if/else)

as

Приведение типов

= += -= *= /= %= &= |= <<= >>=

Присваивание (низший приоритет)

Литералы

Литерал

Тип

45

Целое число в десятичной системе счисления

0x8f51

Основание 16 (шестнадцатеричное) целое число

0b101010

Целое число в двоичной системе счисления

3.14, 58.1e-10

Число с плавающей точкой (вещественное число)

"Привет", "Дарова!"

Строки

"""Привет"""

Многострочная строка

@"Node/Label"

NodePath или StringName

$NodePath

Сокращение для get_node("NodePath")

Типы данных 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.

Комментарии

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

# This is a comment.

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

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

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

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

null

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

bool

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

int

Сокращённо от "integer", содержит целые числа (положительные и отрицательные). Хранится как 64-битное значение, эквивалент "int64_t" в C++.

float

Содержит действительные числа, включая десятичные, используя значения с плавающей запятой. Хранится как 64-битное значение, эквивалент "double" в C++. Примечание: В настоящее время структуры данных, такие как Vector2, Vector3 и PoolRealArray хранятся как 32-битные плавающие одинарные значения "float".

String

Последовательность символов в формате Unicode <https://en.wikipedia.org/wiki/Unicode> `_. Строки могут содержать следующие escape-последовательности:

Escape-последовательность

Означает

\n

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

\t

Символ горизонтальной табуляции

\r

Возврат каретки

\a

Оповещение (звуковой сигнал/звонок)

\b

Возврат

\f

Разрыв страницы

\v

Символ вертикальной табуляции

\"

Двойная кавычка

\'

Одиночная кавычка

\\

Обратная косая черта

\uXXXX

Символ Unicode ``XXXX``(шестнадцатеричный, без учета регистра )

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

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

Vector2

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

Rect2

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

Vector3

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

Transform2D

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

Plane

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

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). Серверы используют общие 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].

Массивы в 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().

Данные

Переменные

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

Приведение типов объектов приводит к одному и тому же объекту, если значение имеет один и тот же тип или подтип приведенного типа.

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

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

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

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

Примечание

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

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

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

Если вы передадите имя перечислению, оно поместит все значения в постоянный словарь (constant dictionary) с этим именем.

Важно

В Godot версии 3.1 и выше ключи именованного перечисления не регистрируются как глобальные константы. Для доступа к ним необходим префикс в виде названия перечисления (Name.KEY); см. пример ниже.

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.

Функции

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

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

В отличие от 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)

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

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

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

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

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

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

You may also wish to check if a value is contained within something. You can use an if statement combined with the in operator to accomplish this:

# 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е

Simple loops are created by using while syntax. Loops can be broken using break or continued using continue (i.e. skipping to the next iteration of the loop without executing any further code in the current iteration):

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

mаtch

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

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

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

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

  1. Замените switch на match.

  2. Уберите case.

  3. Удалите все break. Если вы не хотите чтобы break был по умолчанию, вы можете использовать continue для продолжения проверки.

  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!")
    
  • Шаблон переменной

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

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

    Этот шаблон подходит для любого поиска. Это пишется как единичное подчёркивание.

    Он может быть использован как эквивалент выражения default в выражении switch на других языках.

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

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

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

    Соответствует массиву. Каждый отдельный элемент шаблона массива сам по себе является шаблоном, поэтому вы можете их вкладывать.

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

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

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

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

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

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

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

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

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

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

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

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

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

Классы

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

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

Если сценарий находится в каталоге res://addons/, class_name приведет к тому, что узел будет отображаться в диалоговом окне Создать новый узел, только если сценарий является частью включено плагина редактора. Смотрите: ref:doc_making_plugins для получения дополнительной информации.

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

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

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

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

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

  • Другого файла класса.

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

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

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

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

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

# [...]

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

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

.base_func(args)

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

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

Примечание

Функции по умолчанию, такие как _init, и большинство уведомлений, таких как _enter_tree, _exit_tree, _process, _physics_process и др. вызываются во всех родительских классах автоматически. Нет необходимости вызывать их вручную при перегрузке этих функций.

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

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

В отличие от вызова обычной функции, как в приведенном выше примере с .some_func, если конструктор из унаследованного класса принимает аргументы, то они передаются следующим образом:

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. Если конструктор _init в Idle.gd принимает ноль аргументов, то ему все равно нужно передать некоторое значение в родительский класс State.gd, даже если он ничего не делает. Что подводит нас к тому, что литералы можно передавать не только переменным, но и в базовом конструкторе. Например:

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

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

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

var variable = value setget setterfunc, getterfunc

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

var my_var 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.

Любая из функций setter или getter может быть пропущена:

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

Сеттеры и геттеры полезны при экспорте переменных в редактор в инструментах скриптов или плагинов для проверки ввода.

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

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

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

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

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

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

Если класс наследуется от Reference, то экземпляры будут удалены, когда они больше не будут использоваться. Вместо сборщика мусора происходит подсчет ссылок. По умолчанию, все классы, которые не определяют наследование, расширяются Reference. Если это нежелательно, то класс должен наследовать Object вручную и вызывать instance.free(). Чтобы избежать циклов ссылок, которые не могут быть освобождены, для создания слабых ссылок предусмотрена функция weakref. Вот пример:

extends Node

var my_node_ref

func _ready():
    my_node_ref = weakref(get_node("MyNode"))

func _this_is_called_later():
    var my_node = my_node_ref.get_ref()
    if my_node:
        my_node.do_something()

Или же, если ссылки не используются, is_instance_valid(instance) может быть использован для проверки того, был ли освобожден объект.

Сигналы

Сигналы это способ отправки уведомлений от объекта, на которые могут реагировать другие объекты. Чтобы создать собственные сигналы для класса, используйте ключевое слово signal.

extends Node


# A signal named health_depleted.
signal health_depleted

Примечание

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

Вы можете подключать эти сигналы к методам точно так же, как вы подключаете встроенные сигналы узлов типа Button или RigidBody.

В примере ниже мы соединили сигнал health_depleted от узла Character с узлом Game. Когда узел Character отправляет сигнал, вызывается игровой узел _on_Character_health_depleted:

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

Вы можете отправлять вместе с сигналом столько аргументов, сколько хотите.

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

В нашем скрипте Character.gd мы определяем сигнал health_changed и отправляем его вместе с Object.emit_signal(), и узел Game выше по дереву сцен мы соединяем с Lifebar используя метод Object.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.
    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)
...

Примечание

Чтобы использовать сигналы, ваш класс должен расширить класс Object``или любой другой тип, расширяя его в виде ``Node, KinematicBody, Control...

В узле Game мы берём узлы Character и Lifebar, затем соединяем символ, отправляющий сигнал, с получателем, в нашем случае с узлом Lifebar.

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

Это позволяет 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.connect("health_changed", battle_log_node, "_on_Character_health_changed", [character_node.name])

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

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

Сопрограммы с промежуточным возвратом (yield)

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

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:

func co_func():
    for i in range(1, 5):
        print("Turn %d" % i)
        yield();


func _ready():
    var co = co_func();
    while co is GDScriptFunctionState && co.is_valid():
        co = co.resume();

Сопрограммы и сигналы

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

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

# Resume execution when animation is done playing.
yield(get_node("AnimationPlayer"), "animation_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 будет продолжаться только после нажатия обеих кнопок.

Вы также можете получить аргумент сигнала, когда он передается объектом:

# Wait for when any node is added to the scene tree.
var node = yield(get_tree(), "node_added")

Если передано несколько аргументов, то yield вернёт массив из этих аргументов:

signal done(input, processed)

func process_input(input):
    print("Processing initialized")
    yield(get_tree(), "idle_frame")
    print("Waiting")
    yield(get_tree(), "idle_frame")
    emit_signal("done", input, "Processed " + input)


func _ready():
    process_input("Test") # Prints: Processing initialized
    var data = yield(self, "done") # Prints: waiting
    print(data[1]) # Prints: Processed Test

Если вы не уверены, может ли функция вернуть промежуточный результат или нет, или может ли он вернуться несколько раз, вы можете привести промежуточный результат к сигналу completed через условие:

func generate():
    var result = rand_range(-1.0, 1.0)

    if result < 0.0:
        yield(get_tree(), "idle_frame")

    return result


func make():
    var result = generate()

    if result is GDScriptFunctionState: # Still working.
        result = yield(result, "completed")

    return result

Это гарантирует то, что функция возвращает все, что предполагалось вернуть, независимо от того, использовались ли внутри сопрограммы. Обратите внимание, что использование while` было бы здесь лишним, поскольку сигнал` completed` посылается только тогда, когда у функции больше не остается промежуточных возвратов.

ключевое слово onready

При использовании узлов часто возникает желание сохранить ссылки на части сцены в переменной. Поскольку конфигурирование сцен гарантировано только при входе в активное дерево сцен, дочерние узлы могут быть получены только при вызове функции Node._ready().

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. If 'i' is not 0, an assertion error will occur.
assert(i == 0)

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