Статична типізація в GDScript

У цьому посібнику ви дізнаєтесь:

  • Як використовувати типи в GDScript
  • Що статичні типи можуть допомогти вам уникнути помилок

Де і як ви користуєтеся цією новою мовною функцією, залежить тільки від вас: ви можете використовувати її лише в деяких чутливих файлах GDScript, користуватися скрізь або писати код, як раніше!

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

Примітка

Типізований GDScript доступний з Godot 3.1.

Короткий огляд статичної типізації

With typed GDScript, Godot can detect even more errors as you write code! It gives you and your teammates more information as you’re working, as the arguments“ types show up when you call a method.

Imagine you’re programming an inventory system. You code an Item node, then an Inventory. To add items to the inventory, the people who work with your code should always pass an Item to the Inventory.add method. With types, you can enforce this:

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

Static types also give you better code completion options. Below, you can see the difference between a dynamic and a static typed completion options for a class called PlayerController.

You’ve probably stored a node in a variable before, and typed a dot to be left with no autocomplete suggestions:

code completion options for dynamic

This is due to dynamic code. Godot cannot know what node or value type you’re passing to the function. If you write the type explicitly however, you will get all public methods and variables from the node:

code completion options for typed

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

Overall, typed programming gives you a more structured experience. It helps prevent errors and improves the self-documenting aspect of your scripts. This is especially helpful when you’re working in a team or on a long-term project: studies have shown that developers spend most of their time reading other people’s code, or scripts they wrote in the past and forgot about. The clearer and the more structured the code, the faster it is to understand, the faster you can move forward.

Як використовувати статичну типізацію

To define the type of a variable or a constant, write a colon after the variable’s name, followed by its type. E.g. var health: int. This forces the variable’s type to always stay the same:

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.

Imagine an Enemy in your game, that extends Area2D. You want it to collide with the Player, a KinematicBody2D with a script called PlayerController attached to it. You use the on_body_entered signal to detect the collision. With typed code, the body you detect is going to be a generic PhysicsBody2D, and not your PlayerController on the _on_body_entered callback.

You can check if this PhysicsBody2D is your Player with the as casting keyword, and using the colon : again to force the variable to use this type. This forces the variable to stick to the PlayerController type:

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

    player.damage()

As we’re dealing with a custom type, if the body doesn’t extend PlayerController, the playervariable will be set to null. We can use this to check if the body is the player or not. We will also get full autocompletion on the player variable thanks to that cast.

Примітка

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.

This happens when you get a child node. Let’s take a timer for example: with dynamic code, you can get the node with $Timer. GDScript supports duck-typing, so even if your timer is of type Timer, it is also a Node and an Object, two classes it extends. With dynamic GDScript, you also don’t care about the node’s type as long as it has the methods you need to call.

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

Unsafe line (line 7) vs Safe Lines (line 6 and 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 I 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.

Typed code takes a little more writing, but you get the benefits we discussed above. Here’s an example of the same, empty script, in a dynamic style:

extends Node


func _ready():
    pass


func _process(delta):
    pass

І в статичному стилі:

extends Node


func _ready() -> void:
    pass


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

As you can see, you can also use types with the engine’s virtual methods. Signal callbacks, like any methods, can also use types. Here’s a body_entered signal in a dynamic style:

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)

The bullet variable could hold any CollisionObject2D here, but we make sure it is our Bullet, a node we created for our project. If it’s anything else, like an Area2D, or any node that doesn’t extend Bullet, the bullet variable will be null.

Система попередження

Примітка

Documentation about the GDScript warning system has been moved to GDScript warning system.

Cases where you can’t specify types

To wrap up this introduction, let’s cover a few cases where you can’t use type hints. All the examples below will trigger errors.

You can’t use Enums as types:

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

You can’t specify the type of individual members in an array. This will give you an error:

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

You can’t force the assignment of types in a for loop, as each element the for keyword loops over already has a different type. So you cannot write:

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

Two scripts can’t depend on each other in a cyclic fashion:

# Player.gd

extends Area2D
class_name Player


var rifle: Rifle
# Rifle.gd

extends Area2D
class_name Rifle


var player: Player

Підсумок

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