Статична типізація в 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. Вбудовані

  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

Variable casting

Type casting is a key concept in typed languages. Casting is the conversion of a value from one type to another.

Уявіть у своїй грі Ворога, extends Area2D. Ви хочете, щоб він стикався з гравцем, KinematicBody2D зі скриптом, який називається PlayerController. Ви використовуєте сигнал on_body_entered для виявлення зіткнень. З типізованим кодом, виявлене тіло, буде загальним PhysicsBody2D, а не вашим PlayerController при зворотному виклику _on_body_entered.

Ви можете перевірити, чи є цей PhysicsBody2D вашим гравцем за допомогою ключового слова as, і знову використати двокрапку :, щоб змусити змінну використовувати цей тип. Це змушує змінну дотримуватися типу PlayerController:

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

    player.damage()

Оскільки ми маємо справу з власним типом, якщо body не розширення PlayerController, змінна player буде встановлена на null. Ми можемо використати це, щоб перевірити, чи є тіло гравцем, чи ні. Ми також отримаємо повне автодоповнення змінної гравця завдяки цьому касту.

Примітка

If you try to cast with a built-in type and it fails, Godot will throw an error.

Безпечні рядки

You can also use casting to ensure safe lines. Safe lines are a new tool in Godot 3.1 to tell you when ambiguous lines of code are type-safe. As you can mix and match typed and dynamic code, at times, Godot doesn't have enough information to know if an instruction will trigger an error or not at runtime.

Це відбувається, коли ви отримуєте дочірній вузол. Візьмемо для прикладу таймер: за допомогою динамічного коду ви можете отримати вузол з допомогою``$Timer``. GDScript підтримує `качину типізацію<https://stackoverflow.com/a/4205163/8125343>`__, тому навіть якщо ваш таймер типу Timer, він також є Node і Object, два класи він розширює. За допомогою динамічного GDScript ви також не піклуєтесь про тип вузла, якщо він має методи, необхідні для виклику.

You can use casting to tell Godot the type you expect when you get a node: ($Timer as Timer), ($Player as KinematicBody2D), etc. Godot will ensure the type works and if so, the line number will turn green at the left of the script editor.

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

Типізований, або динамічний: дотримуйтеся одного стилю

Typed GDScript and dynamic GDScript can coexist in the same project. But it's recommended to stick to either style for consistency in your codebase, and for your peers. It's easier for everyone to work together if you follow the same guidelines, and faster to read and understand other people's code.

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

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