Static typing in GDScript

В этом руководстве вы узнаете:

  • Как использовать типы в GDScript
  • Что статические типы могут помочь вам избежать ошибок

Где и как вы используете эту новую функцию языка полностью зависит от Вас: вы можете использовать ее только в некоторых файлах GDScript, использовать везде или писать код, как вы всегда делали!

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

Примечание

Типизированный GDScript доступен начиная с Godot 3.1.

Краткий обзор статической типизации

С типизированным GDScript, Godot может обнаружить еще больше ошибок при написании кода! Это дает вам и вашей команде больше информации во время работы, поскольку типы аргументов отображаются при вызове метода.

Представьте, что вы программируете систему инвентаризации. Вы запрограммировали узел Item, а затем Inventory. Чтобы добавить предметы в инвентарь, люди, работающие с вашим кодом, должны всегда передавать Item методу Inventory.add. Используя типы вы можете обеспечить это:

# In Item.gd
class_name Item

# In Inventory.gd
class_name Inventory

func add(reference: Item, amount: int = 1):
    var item = find_item(reference)
    if not item:
        item = _instance_item_from_db(reference)
    item.amount += amount

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

Статические типы также дают вам лучшие варианты завершения кода. Ниже вы можете увидеть разницу между динамической и статической типизацией вариантов завершения для класса под названием PlayerController.

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

code completion options for dynamic

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

code completion options for typed

В будущем, типизированный GDScript также увеличит производительность кода: компиляция Just-In-Time и другие улучшения компилятора уже в планах!

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

Как использовать статическую типизацию

Чтобы определить тип переменной или константы, запишите двоеточие после имени переменной, а затем ее тип. Например var health: int. Это заставляет тип переменной всегда оставаться неизменным:

var damage: float = 10.5
const MOVE_SPEED: float = 50.0

Godot попытается определить тип самостоятельно, если вы напишете двоеточие, но не укажите тип:

var life_points := 4
var damage := 10.5
var motion := Vector2()

В настоящее время вы можете использовать три вида… типов:

  1. Built-in
  2. Основные классы и узлы (Object, Node, Area2D, Camera2D, и т.д.)
  3. Ваши собственные, индивидуальные классы. Посмотрите на новую функцию class_name для регистрации типов в редакторе.

Примечание

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

Пользовательские типы переменных

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

const Rifle = preload('res://player/weapons/Rifle.gd')
var my_rifle: Rifle

Второй метод заключается в использовании ключевого слова class_name при создании. Для приведенного выше примера ваш Rifle.gd будет выглядеть так:

extends Node2D
class_name Rifle

Если вы используете class_name, Godot глобально регистрирует тип Rifle в редакторе, и вы можете использовать его где угодно, без предварительной загрузки в константу:

var my_rifle: Rifle

Приведение переменной

Приведение типов является ключевым понятием в типизированных языках. Приведение - это преобразование значения из одного типа в другой.

Представьте себе врага в вашей игре, который расширяет Area2D. Вы хотите, чтобы он сталкивался с Player-ом : KinematicBody2D со скриптом под названием PlayerController, прикрепленным к нему. Вы используете сигнал on_body_entered для обнаружения столкновений. С типизированным кодом тело, которое вы обнаружите, будет общим PhysicsBody2D, а не вашим PlayerController при обратном вызове _on_body_entered.

Вы можете проверить, является ли этот PhysicsBody2D вашим Player-ом используя ключевое слово as и снова использовать двоеточие :, чтобы заставить переменную использовать этот тип. Это заставляет переменную придерживаться типа PlayerController:

func _on_body_entered(body: PhysicsBody2D) -> void:
    var player := body as PlayerController
    if not player:
        return
    player.damage()

Так как мы имеем дело с пользовательским типом, если body не расширяет PlayerController, значение переменной player будет установлено null. Мы можем использовать это, чтобы проверить, является ли тело Player-ом или нет. Мы также получим полное авто-заполнение переменной Player-а благодаря этому исполнению.

Примечание

Если вы попытаетесь выполнить приведение со встроенным типом, и это не удастся, Godot выдаст ошибку.

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

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

Это происходит, когда вы получаете дочерний узел. Возьмем, к примеру, таймер: с динамическим кодом вы можете получить узел с помощью $Timer. GDScript поддерживает Утиную типизацию, поэтому даже если ваш таймер имеет тип Timer, он также является Node и Object, два класса которые он расширяет. В динамическом GDScript вы также не заботитесь о типе узла, если у него есть необходимые вам методы для вызова.

Вы можете использовать приведение, чтобы сообщить Godot-у тип, который вы ожидаете, когда получаете узел: ($Timer as Timer), ($Player as KinematicBody2D) и т.д. Godot обеспечит работу типа и, если это так, то номер строки слева в редакторе скриптов станет зеленым.

Safe vs Unsafe Line

Безопасные строки против Небезопасных строк

Примечание

Вы можете отключить безопасные строки или изменить их цвет в настройках редактора.

Определите тип возвращаемого значения функции с помощью стрелки ->

To define the return type of a function, write a dash and a right angle bracket -> after its declaration, followed by the return type:

func _process(delta: float) -> void:
    pass

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

func hit(damage: float) -> bool:
    health_points -= damage
    return health_points <= 0

Вы также можете использовать свои собственные узлы в качестве возвращаемых типов:

# Inventory.gd

# Adds an item to the inventory and returns it.
func add(reference: Item, amount: int) -> Item:
    var item: Item = find_item(reference)
    if not item:
        item = ItemDatabase.get_instance(reference)
    item.amount += amount
    return item

Типизированный или динамичный: придерживайтесь одного стиля

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

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

extends Node
    func _ready():
        pass
    func _process(delta):
        pass

И со статической типизацией:

extends Node
    func _ready() -> void:
        pass
    func _process(delta: float) -> void:
        pass

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

func _on_Area2D_body_entered(body):
    pass

И тот же обратный вызов, с типовыми подсказками:

func _on_area_entered(area: CollisionObject2D) -> void:
    pass

You’re free to replace, e.g. the CollisionObject2D, with your own type, to cast parameters automatically:

func _on_area_entered(bullet: Bullet) -> void:
    if not bullet:
        return
    take_damage(bullet.damage)

Переменная bullet может содержать любой CollisionObject2D здесь, но мы должны убедиться, что это наш Bullet, узел, который мы создали для нашего проекта. Если это что-то еще, например, Area2D или любой другой узел, который не расширяет Bullet, переменная bullet будет иметь значение null.

Система предупреждений

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

Вы можете настроить предупреждения в Настройках Проекта, в новом разделе под названием GDScript:

warning system project settings

warning system project settings

Вы можете найти список предупреждений для активного файла GDScript в строке состояния редактора сценариев. В приведенном ниже примере есть 3 предупреждения:

warning system example

warning system example

Чтобы игнорировать определенные предупреждения в одном файле, вставьте специальный комментарий в форме #warning-ignore:warning-id или нажмите на ссылку игнорирования справа от описания предупреждения. Godot добавит комментарий над соответствующей строкой, и код больше не будет вызывать соответствующее предупреждение:

warning system ignore example

warning system ignore example

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

warnings as errors

предупреждения как ошибки

Случаи, когда вы не можете указать типы

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

Вы не можете использовать Перечисления как типы:

enum MoveDirection {UP, DOWN, LEFT, RIGHT}
var current_direction: MoveDirection

Вы не можете указать тип отдельных членов в массиве. Это приведет к ошибке:

var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]

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

var names = ['John', 'Marta', 'Samantha', 'Jimmy']
for name: String in names:
    pass

Два скрипта не могут циклически зависеть друг от друга:

# Player.gd
extends Area2D
class_name Player

var rifle: Rifle
# Rifle.gd
extends Area2D
class_name Rifle

var player: Player

Подведение итогов

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