Tipagem estática em GDScript

Neste guia, você aprenderá:

  • Como usar tipos no GDScript

  • Que os tipos estáticos podem ajudá-lo a evitar erros

A decisão de onde e como você usa esse novo recurso de linguagem é inteiramente sua: você pode usá-lo apenas em alguns arquivos GDScript sensíveis, usá-lo em qualquer lugar ou escrever códigos como sempre fez!

Tipos estáticos podem ser usados em variáveis, constantes, funções, parâmetros e tipos de retorno.

Nota

O GDScript tipado está disponível desde Godot 3.1.

Uma breve olhada na tipagem estática

Com GDScript tipado, Godot pode detectar ainda mais erros enquanto você escreve o código! Ele fornece a você e a seus colegas de equipe mais informações enquanto você trabalha, pois os tipos de argumentos aparecem quando você chama um método.

Imagine que você esteja programando um sistema de inventário. Você codifica um nó Item, depois um Inventário. Para adicionar itens ao inventário, as pessoas que trabalham com seu código devem sempre passar um Item para o método Inventário.add. Com tipos, você pode impor isso:

# 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

Outra vantagem significativa do GDScript tipado é o novo sistema de alerta. A partir da versão 3.1, Godot dá avisos sobre o seu código à medida que você o escreve: o motor identifica seções de seu código que podem levar a problemas em tempo de execução, mas permite que você decida se deseja ou não deixar o código como está. Mais sobre isso em um momento.

Tipos estáticos também oferecem opções melhores de conclusão de código. Abaixo, você pode ver a diferença entre opções de conclusão tipadas dinâmica e estática completa para uma classe chamada PlayerController.

Você provavelmente armazenou um nó em uma variável antes e digitou um ponto para ficar sem sugestões de preenchimento automático:

code completion options for dynamic

Isto é devido a código dinâmico. O Godot não pode saber qual nó ou tipo de valor você está passando para a função. Se você escrever o tipo explicitamente no entanto, você obterá todos os métodos e variáveis públicos do nó:

code completion options for typed

No futuro, o GDScript tipado também aumentará o desempenho do código: a compilação Just-In-Time e outras melhorias do compilador já estão no roteiro!

No geral, a programação tipada proporciona uma experiência mais estruturada. Ajuda a evitar erros e melhora o aspecto de autodocumentação dos seus scripts. Isso é especialmente útil quando você trabalha em equipe ou em um projeto de longo prazo: estudos mostram que os desenvolvedores passam a maior parte do tempo lendo o código de outras pessoas ou scripts que escreveram no passado e esqueceram. Quanto mais claro e mais estruturado o código, mais rápido será entender, mais rápido você poderá avançar.

Como usar a tipagem estática

Para definir o tipo de uma variável ou uma constante, escreva dois pontos após o nome da variável, seguido por seu tipo. Por exemplo. var health: int. Isso força o tipo da variável a permanecer sempre o mesmo:

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

Godot tentará inferir tipos se você escrever dois pontos, mas omite o tipo:

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

Atualmente você pode usar três tipos de… tipos:

  1. Embutido

  2. Classes e nós centrais (Objeto, , Area2D, Camera2D, etc.)

  3. Suas próprias classes personalizadas. Veja o novo recurso :ref:'class_name <doc_gdscript_basics_class_name>' para registrar tipos no editor.

Nota

Você não precisa escrever inferências de tipo para constantes, já que o Godot o define automaticamente a partir do valor atribuído. Mas você ainda pode fazer isso para tornar a intenção do seu código mais clara.

Tipos de variável personalizados

Você pode usar qualquer classe, incluindo suas classes personalizadas, como tipos. Existem duas maneiras de usá-los em scripts. O primeiro método é pré-carregar o script que você deseja usar como um tipo em uma constante:

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

O segundo método é usar a palavra-chave class_name quando você cria. Para o exemplo acima, o seu Rifle.gd ficaria assim:

class_name Rifle
extends Node2D

Se você usa class_name, o Godot registra o tipo Rifle globalmente no editor, e você pode usá-lo em qualquer lugar, sem ter que previamente carregá-lo em uma constante:

var my_rifle: Rifle

Conversão de variáveis

A conversão de tipos é um conceito-chave em linguagens tipados. Casting é a conversão de um valor de um tipo para outro.

Imagine um Inimigo no seu jogo, que extends Area2D. Você quer que ele colida com o Jogador, um KinematicBody2D com um script chamado PlayerController atrelado a ele.

Você pode verificar se este PhysicsBody2D é o seu Jogador com a palavra-chave as e usando os dois pontos : novamente para forçar a variável a usar este tipo. Isso força a variável a se ater ao tipo PlayerController:

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

    player.damage()

Como nós estamos lidando com um tipo personalizado, se o body não extende PlayerController, a variável playervai ser definida como null. Nós podemos usar isso para checar se o body é o player or não. Nós também poderemos usar preenchimento automático completo no variável player graças a essa conversão.

Nota

Se você tentar moldar com um tipo embutido e falhar, Godot lançará um erro.

Linhas seguras

Você também pode usar casting para garantir linhas seguras. As linhas seguras são uma nova ferramenta do Godot 3.1 para lhe dizer quando linhas de código ambíguas são seguras para o tipo. Como você pode misturar e combinar códigos tipados e dinâmicos, às vezes, Godot não tem informações suficientes para saber se uma instrução disparará um erro ou não em tempo de execução.

Isso acontece quando você pega um nó filho. Vamos tomar um timer como exemplo: com código dinâmico, você pode pegar o nó usando $Timer. GDScript suporta duck-typing, então mesmo que o seu timer seja do tipo Timer, ele também é um Node e um Object, extendendo duas classes. Com GDScript dinâmico, você não se importa com o tipo do nó desde que ele tenha os métodos que você quer chamar.

Você pode usar casting para dizer ao Godot o tipo que você espera quando você obtém um nó: ($Timer as Timer), ($Player as KinematicBody2D) etc. O Godot irá verificar que o tipo funciona e, se funcionar, o número da linha ficará verde à esquerda do editor de script.

Linha insegura vs. segura

Linha não segura (linha 7) vs Linhas Seguras (linha 6 e 8)

Nota

Você pode desativar linhas seguras ou alterar suas cores nas configurações do editor.

Defina o tipo de retorno de uma função com a seta ->

Para definir o tipo de retorno de uma função, coloque um traço e o símbolo de menor que -> depois de sua declaração, seguido pelo tipo de retorno:

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

O tipo void significa que a função não retorna nada. Você pode usar qualquer tipo, como com variáveis:

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

Você também pode usar seus próprios nós como tipos de retorno:

# 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

Tipada ou dinâmica: Adote um estilo

GDScript tipado e GDScript dinâmico podem coexistir em um mesmo projeto. Mas é recomendado adotar um dos dois estilos para manter a consistência do seu código base. Facilita para todos trabalharem juntos se vocês seguirem as mesmas diretrizes, e acelera a leitura e o entendimento do código de outras pessoas.

Código tipado leva um pouco mais de escrita, mas você terá os benefícios discutidos acima. Aqui está um exemplo do mesmo script vazio utilizando a tipagem dinâmica:

extends Node


func _ready():
    pass


func _process(delta):
    pass

E com a tipagem estática:

extends Node


func _ready() -> void:
    pass


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

Como você pode ver, você também pode usar tipos com os métodos virtuais da engine. Chamadas de retorno de sinal, como quaisquer métodos, também podem usar tipos. Aqui um sinal body_entered com a tipagem dinâmica:

func _on_Area2D_body_entered(body):
    pass

E a mesma chamada de retorno, com as inferências de tipo:

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

Você é livre pra substituir, por exemplo, o CollisionObject2D, com seu próprio tipo, para converter os parâmetros automaticamente:

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

    take_damage(bullet.damage)

A variável bullet pode guardar qualquer objeto CollisionObject2D aqui, mas temos que garantir que é o nosso Bullet, um nó que criamos para o nosso projeto. Se for qualquer outra coisa, como uma Area2D, ou qualquer nó que não estenda Bullet, a variável bullet será null.

Sistema de alertas

Nota

A documentação sobre o sistema de avisos do GDScript foi movida para Sistema de alertas do GDScript.

Casos onde você não pode especificar tipos

Para concluir essa introdução, vamos abordar alguns poucos casos onde você não pode usar inferências de tipo. Todos os exemplos abaixo irão acionar erros.

Você não pode usar Enums como tipos:

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

Você não pode especificar o tipo de membros individuais em um array. Isso lhe dará um erro:

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

Você não pode forçar a atribuição de tipos em um loop for, pois cada elemento em que o loop for executa em loop já possui um tipo diferente. Então você não pode escrever:

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

Dois scripts não podem depender um do outro de maneira cíclica:

# Player.gd

extends Area2D
class_name Player


var rifle: Rifle
# Rifle.gd

extends Area2D
class_name Rifle


var player: Player

Resumo

GDScript tipado é uma ferramenta poderosa. Disponível na versão 3.1 do Godot, lhe ajudará a escrever um código mais estruturado, evitando erros comuns, e a criar sistemas escaláveis. No futuro, a tipagem estática também te trará um bom aumento de desempenho graças às futuras otimizações do compilador.