Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Tipagem estática em GDScript

Neste guia, você aprenderá:

  • Como usar tipagem estática em GDScript;

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

  • que a tipagem estática melhora sua experiência com o editor.

A decisão de onde e como você usa esse recurso de linguagem é inteiramente sua: você pode usá-lo apenas em alguns arquivos GDScript sensíveis, usá-lo em qualquer lugar, ou não usá-los.

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

Uma breve olhada na tipagem estática

Com a tipagem estática, o GDScript pode detectar mais erros sem sequer executar o código. Além disso, as dicas de tipo dão a você e aos seus colegas de equipe mais informações enquanto trabalham, já que os tipos dos argumentos aparecem quando você chama um método. A tipagem estática melhora o autocompletar do editor e a documentação dos seus scripts.

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

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

Tipos estáticos também fornecem melhores opções de autocompletar código. Abaixo, você pode ver a diferença entre as opções de autocompletar de tipagem dinâmica e estática.

Você provavelmente já se deparou com a falta de sugestões de preenchimento automático após um ponto:

Opções de preenchimento para código tipado dinamicamente.

Isso se deve ao código dinâmico. O Godot não tem como saber qual tipo de valor você está passando para a função. Se você escrever o tipo explicitamente, no entanto, você obterá todos os métodos, propriedades, constantes, etc. a partir do valor:

Opções de preenchimento para código tipado estaticamente.

Dica

Se você prefere tipagem estática, recomendamos habilitar a configuração do editor Editor de Texto > Completar > Adicionar dicas de tipo. Considere também habilitar alguns avisos que são desativados por padrão.

Além disso, o GDScript tipado melhora o desempenho ao usar opcodes otimizados quando os tipos de operandos/argumentos são conhecidos em tempo de compilação. Mais otimizações para o GDScript estão planejadas para o futuro, como a compilação JIT/AOT.

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, parâmetro ou constante, escreva dois pontos após o nome, seguido pelo seu tipo. Ex: 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
func sum(a: float = 0.0, b: float = 0.0) -> float:
    return a + b

Godot will try to infer the type when you write a colon, but omit the type:

var damage := 10.5
const MOVE_SPEED := 50.0
func sum(a := 0.0, b := 0.0) -> float:
    return a + b

Nota

  1. Não há diferença entre = e := para constantes.

  2. Você não precisa escrever dicas de tipo para constantes, pois o Godot as 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. Além disso, isso é útil para arrays tipados (como const A: Array[int] = [1, 2, 3]), já que arrays não tipados são usados por padrão.

O que pode ser uma dica de tipo

Aqui está uma lista completa do que pode ser usado como uma dica de tipo:

  1. Variant. Qualquer tipo. Na maioria dos casos, isso não é muito diferente de uma declaração não tipada, mas aumenta a legibilidade. Como tipo de retorno, força a função a retornar explicitamente algum valor.

  2. (Apenas tipo de retorno) void. Indica que a função não retorna nenhum valor.

  3. Tipos integrados.

  4. Classes nativas (Object, Node, Area2D, Camera2D, etc.).

  5. Classes globais.

  6. Classes internas (inner classes).

  7. Enums globais, nativos e customizados nomeados. Note que um tipo enum é apenas um int, não há garantia de que o valor pertença ao conjunto de valores do enum.

  8. Constantes (incluindo as locais) se contiverem uma classe ou enum pré-carregado.

Você pode usar qualquer classe, incluindo suas classes personalizadas, como tipos. Existem duas maneiras de usá-las em scripts. O primeiro método é pré-carregar (preload) o script que você deseja usar como 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 o script. Para o exemplo acima, seu rifle.gd ficaria assim:

class_name Rifle
extends Node2D

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

var my_rifle: Rifle

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

Para definir o tipo de retorno de uma função, escreva um hífen e um sinal de maior -> após a 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, assim como com as variáveis:

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

Você também pode usar suas próprias classes como tipos de retorno:

# 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

Covariância e contravariância

Ao herdar métodos de uma classe base, você deve seguir o Princípio de Substituição de Liskov.

Covariância: Quando você herda um método, pode especificar um tipo de retorno que seja mais específico (subtipo) do que o método pai.

Contravariância: Quando você herda um método, pode especificar um tipo de parâmetro que seja menos específico (supertipo) do que o método pai.

Exemplo:

class_name Parent


func get_property(param: Label) -> Node:
    # ...
class_name Child extends Parent


# `Control` is a supertype of `Label`.
# `Node2D` is a subtype of `Node`.
func get_property(param: Control) -> Node2D:
    # ...

Especifique o tipo de elemento de um Array

Para definir o tipo de um Array, envolva o nome do tipo entre [].

O tipo de um array se aplica a variáveis de loop for, bem como a alguns operadores como [], [...] = (atribuição) e +. Métodos de array (como push_back) e outros operadores (como ==) ainda permanecem não tipados. Tipos integrados, classes nativas, customizadas e enums podem ser usados como tipos de elementos. Tipos de array aninhados (como Array[Array[int]]) não são suportados.

var scores: Array[int] = [10, 20, 30]
var vehicles: Array[Node] = [$Car, $Plane]
var items: Array[Item] = [Item.new()]
var array_of_arrays: Array[Array] = [[], []]
# var arrays: Array[Array[int]] -- disallowed

for score in scores:
    # score has type `int`

# The following would be errors:
scores += vehicles
var s: String = scores[0]
scores[0] = "lots"

Desde o Godot 4.2, você também pode especificar um tipo para a variável de loop em um loop for. Por exemplo, você pode escrever:

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

O array continuará não tipado, mas a variável name dentro do loop for sempre será do tipo String.

Especifique o tipo de elemento de um Dictionary

Para definir o tipo das chaves e valores de um Dictionary, envolva os nomes dos tipos entre [] e separe o tipo da chave e do valor por uma vírgula.

O tipo do valor de um dicionário se aplica a variáveis de loop for, bem como a alguns operadores como [] e [...] = (atribuição). Métodos de dicionário que retornam valores e outros operadores (como ==) ainda permanecem não tipados. Tipos integrados, classes nativas, customizadas e enums podem ser usados como tipos de elementos. Coleções tipadas aninhadas (como Dictionary[String, Dictionary[String, int]]) não são suportadas.

var fruit_costs: Dictionary[String, int] = { "apple": 5, "orange": 10 }
var vehicles: Dictionary[String, Node] = { "car": $Car, "plane": $Plane }
var item_tiles: Dictionary[Vector2i, Item] = { Vector2i(0, 0): Item.new(), Vector2i(0, 1): Item.new() }
var dictionary_of_dictionaries: Dictionary[String, Dictionary] = { { } }
# var dicts: Dictionary[String, Dictionary[String, int]] -- disallowed

for fruit in fruit_costs:
    # `fruit` has type `String`

# The following would be errors:
fruit_costs["pear"] += vehicles
var s: String = fruit_costs["apple"]
fruit_costs["orange"] = "lots"

Conversão de tipo (Type casting)

A conversão de tipo (casting) é um conceito importante em linguagens tipadas. Casting é a conversão de um valor de um tipo para outro.

Imagine um Enemy no seu jogo, que extends Area2D. Você quer que ele colida com o Player, um CharacterBody2D com um script chamado PlayerController anexado a ele. Você usa o sinal body_entered para detectar a colisão. Com código tipado, o corpo que você detecta será um PhysicsBody2D genérico, e não o seu PlayerController no callback _on_body_entered.

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

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

    player.damage()

Como estamos lidando com um tipo personalizado, se o body não estender PlayerController, a variável player será definida como null. Podemos usar isso para verificar se o corpo é o jogador ou não. Também teremos autocompletar total na variável player graças a esse cast.

Nota

A palavra-chave as converte silenciosamente a variável para null em caso de incompatibilidade de tipos em tempo de execução, sem gerar um erro ou aviso. Embora isso possa ser conveniente em alguns casos, também pode levar a bugs. Use a palavra-chave as apenas se esse comportamento for intencional. Uma alternativa mais segura é usar a palavra-chave is:

if not (body is PlayerController):
    push_error("Bug: body is not PlayerController.")

var player: PlayerController = body
if not player:
    return

player.damage()

Você também pode simplificar o código usando o operador is not:

if body is not PlayerController:
    push_error("Bug: body is not PlayerController")

Alternativamente, você pode usar a declaração assert():

assert(body is PlayerController, "Bug: body is not PlayerController.")

var player: PlayerController = body
if not player:
    return

player.damage()

Nota

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

Linhas seguras

Você também pode usar o casting para garantir linhas seguras (safe lines). Linhas seguras são uma ferramenta para indicar quando linhas de código ambíguas são seguras em relação aos tipos. Como você pode misturar e combinar código tipado e dinâmico, às vezes o Godot não tem informações suficientes para saber se uma instrução causará 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 o casting para dizer ao Godot o tipo que você espera quando obtém um nó: ($Timer as Timer), ($Player as CharacterBody2D), etc. O Godot garantirá que o tipo funcione e, se for o caso, 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

Linhas seguras (safe lines) nem sempre significam um código melhor ou mais confiável. Veja a nota acima sobre a palavra-chave as. Por exemplo:

@onready var node_1 := $Node1 as Type1 # Safe line.
@onready var node_2: Type2 = $Node2 # Unsafe line.

Mesmo que a declaração de node_2 esteja marcada como uma linha insegura, ela é mais confiável do que a declaração de node_1. Porque se você alterar o tipo do nó na cena e acidentalmente esquecer de alterá-lo no script, o erro será detectado imediatamente quando a cena for carregada. Diferente do node_1, que será silenciosamente convertido para null e o erro só será detectado mais tarde.

Nota

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

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 exige um pouco mais de escrita, mas você obtém os benefícios que discutimos acima. Aqui está um exemplo do mesmo script vazio, em estilo dinâmico:

extends Node


func _ready():
    pass


func _process(delta):
    pass

E com 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. Callbacks de sinal, como quaisquer métodos, também podem usar tipos. Aqui está um sinal body_entered em estilo dinâmico:

func _on_area_2d_body_entered(body):
    pass

E o mesmo callback, com dicas de tipo:

func _on_area_2d_body_entered(body: PhysicsBody2D) -> void:
    pass

Sistema de alertas

Nota

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

O Godot emite avisos sobre o seu código enquanto você o escreve. A engine identifica partes do código que podem causar problemas durante a execução, mas deixa você decidir se quer ou não mantê-lo como está.

Temos uma série de avisos voltados especificamente para usuários de GDScript tipado. Por padrão, esses avisos estão desativados; você pode ativá-los nas Configurações do Projeto (Debug > GDScript, certifique-se de que as Advanced Settings estejam ativadas).

Você pode ativar o aviso UNTYPED_DECLARATION se quiser usar sempre tipos estáticos. Além disso, pode ativar o aviso INFERRED_DECLARATION se preferir uma sintaxe mais legível e confiável, porém mais prolixa.

Os avisos UNSAFE_* tornam as operações inseguras mais visíveis do que as linhas inseguras. Atualmente, os avisos UNSAFE_* não cobrem todos os casos que as linhas inseguras cobrem.

Operações inseguras comuns e suas contrapartes seguras

Métodos de escopo global

Os seguintes métodos de escopo global não são tipados estaticamente, mas possuem contrapartes tipadas disponíveis. Esses métodos retornam valores tipados estaticamente:

Método

Equivalentes tipados estaticamente

abs()

ceil()

clamp()

floor()

lerp()

round()

sign()

snapped()

Ao usar tipagem estática, use os métodos de escopo global tipados sempre que possível. Isso garante que você tenha linhas seguras e se beneficie de instruções tipadas para um melhor desempenho.

Avisos de UNSAFE_PROPERTY_ACCESS e UNSAFE_METHOD_ACCESS

Neste exemplo, nosso objetivo é definir uma propriedade e chamar um método em um objeto que possui um script anexado com class_name MyScript e que extends Node2D. Se tivermos uma referência para o objeto como um Node2D (por exemplo, como ele nos foi passado pelo sistema de física), podemos primeiro verificar se a propriedade e o método existem e então defini-los e chamá-los se existirem:

if "some_property" in node_2d:
    node_2d.some_property = 20  # Produces UNSAFE_PROPERTY_ACCESS warning.

if node_2d.has_method("some_function"):
    node_2d.some_function()  # Produces UNSAFE_METHOD_ACCESS warning.

No entanto, este código produzirá avisos de UNSAFE_PROPERTY_ACCESS e UNSAFE_METHOD_ACCESS, pois a propriedade e o método não estão presentes no tipo referenciado - neste caso, um Node2D. Para tornar essas operações seguras, você pode primeiro verificar se o objeto é do tipo MyScript usando a palavra-chave is e então declarar uma variável com o tipo MyScript na qual você pode definir suas propriedades e chamar seus métodos:

if node_2d is MyScript:
    var my_script: MyScript = node_2d
    my_script.some_property = 20
    my_script.some_function()

Alternativamente, você pode declarar uma variável e usar o operador as para tentar converter o objeto. Você então desejará verificar se a conversão foi bem-sucedida confirmando se a variável foi atribuída:

var my_script := node_2d as MyScript
if my_script != null:
    my_script.some_property = 20
    my_script.some_function()

Aviso de UNSAFE_CAST

Neste exemplo, gostaríamos que o rótulo (label) conectado a um objeto que entra em nossa área de colisão mostrasse o nome da área. Assim que o objeto entra na área de colisão, o sistema de física envia um sinal com um objeto Node2D, e a solução mais direta (mas não estaticamente tipada) para fazer o que queremos poderia ser alcançada assim:

func _on_body_entered(body: Node2D) -> void:
    body.label.text = name  # Produces UNSAFE_PROPERTY_ACCESS warning.

Este pedaço de código produz um aviso de UNSAFE_PROPERTY_ACCESS porque label não está definido em Node2D. Para resolver isso, poderíamos primeiro verificar se a propriedade label existe e convertê-la para o tipo Label antes de definir sua propriedade de texto da seguinte forma:

func _on_body_entered(body: Node2D) -> void:
    if "label" in body:
        (body.label as Label).text = name  # Produces UNSAFE_CAST warning.

No entanto, isso produz um aviso de UNSAFE_CAST porque body.label é de um tipo Variant. Para obter a propriedade com segurança no tipo desejado, você pode usar o método Object.get(), que retorna o objeto como um valor Variant ou retorna null se a propriedade não existir. Você pode então determinar se a propriedade contém um objeto do tipo correto usando a palavra-chave is e, finalmente, declarar uma variável estaticamente tipada com o objeto:

func _on_body_entered(body: Node2D) -> void:
    var label_variant: Variant = body.get("label")
    if label_variant is Label:
        var label: Label = label_variant
        label.text = name

Casos onde você não pode especificar tipos

Para encerrar esta introdução, vamos mencionar os casos onde você não pode usar dicas de tipo. Isso causará um erro de sintaxe.

  1. Você não pode especificar o tipo de elementos individuais em um array ou dicionário:

var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]
var character: Dictionary = {
    name: String = "Richard",
    money: int = 1000,
    inventory: Inventory = $Inventory,
}
  1. Os tipos aninhados não são suportados atualmente:

var teams: Array[Array[Character]] = []

Resumo

O GDScript tipado é uma ferramenta poderosa. Ele ajuda você a escrever um código mais estruturado, evitar erros comuns e criar sistemas escaláveis e confiáveis. Tipos estáticos melhoram o desempenho do GDScript e mais otimizações estão planejadas para o futuro.