Статическая типизация в 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. Your own custom classes. Look at the new class_name feature to register types in the editor.

Примечание

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

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

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

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

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

class_name Rifle
extends Node2D

Если вы используете 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 обеспечит работу типа и, если это так, то номер строки слева в редакторе скриптов станет зеленым.

Unsafe vs Safe Line

Небезопасная строка (строка 7) против Безопасных Строк (строки 6 и 8)

Примечание

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

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

Чтобы определить тип возвращаемого значения функции, напишите тире и угловую скобку показывающую направо -> после ее объявления, а затем тип возвращаемого значения:

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

Вы можете заменить, например, ``CollisionObject2D`, на свой собственный тип, чтобы приводить параметры автоматически:

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

    take_damage(bullet.damage)

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

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

Примечание

Документация о системе предупреждений GDScript была перемещена в Система предупреждений GDScript.

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

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

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

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