Tipado estático en GDScript

En esta guía aprenderás:

  • Cómo usar tipos en GDScript
  • Que los tipos estáticos pueden ayudarte a evitar errores

El lugar y la forma en que use esta nueva funcionalidad de idioma depende totalmente de usted: ¡puede usarla solo en algunos archivos GDScript confidenciales, usarla en todas partes o escribir código como siempre lo hizo!

Los tipos estáticos se pueden usar en variables, constantes, funciones, parámetros y tipos de retorno.

Nota

GDScript de tipado estático está disponible desde Godot 3.1.

Un breve vistazo a los tipos estáticos

Con GDScript tipado, Godot puede detectar aún más errores mientras escribes código. Te da a ti y a tu equipo más información mientras trabajan, ya que los tipos de los argumentos aparecen cuando llamas a un método.

Imagina que estás programando un sistema de inventario. Codificas un nodo Item, luego un nodo Inventory. Para agregar artículos al inventario, las personas que trabajan con su código siempre deben pasar un Item al método `` Inventory.add``. Con los tipos, puedes hacer cumplir esto:

# 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

Otra ventaja significativa de GDScript de tipado estático es el nuevo ** sistema de advertencia **. A partir de la versión 3.1, Godot le advierte sobre su código a medida que lo escribe: el motor identifica las secciones de su código que pueden provocar problemas en el tiempo de ejecución, pero le permite decidir si desea dejar el código como está. Más sobre esto en un momento.

Los tipos estáticos también le dan mejores opciones de completado del código. A continuación, puedes ver la diferencia entre las opciones de completado de tipo dinámico y estático para una clase llamada PlayerController.

Probablemente hayas almacenado un nodo en una variable y escrito un punto esperando sugerencias de autocompletado sin obtener nada:

code completion options for dynamic

Esto se debe al código dinámico. Godot no puede saber qué tipo de nodo o valor está pasando a la función. Sin embargo, si escribe el tipo explícitamente, obtendrás todos los métodos y variables públicos del nodo:

code completion options for typed

En el futuro, GDScript de tipado estático también aumentará el rendimiento del código: ¡la compilación Just-In-Time y otras mejoras del compilador ya están en la hoja de ruta!

En general, la programación de tipado estático le brinda una experiencia más estructurada. Ayuda a prevenir errores y mejora el aspecto de autodocumentación de sus scripts. Esto es especialmente útil cuando trabajas en equipo o en un proyecto a largo plazo: los estudios han demostrado que los desarrolladores dedican la mayor parte de su tiempo a leer el código de otras personas o los scripts que escribieron en el pasado y olvidaron. Cuanto más claro y más estructurado sea el código, más rápido será de entender, más rápido podrá avanzar.

Cómo utilizar tipos estáticos

Para definir el tipo de una variable o una constante, escriba dos puntos después del nombre de la variable, seguido de su tipo. P.ej. var health: int. Esto obliga al tipo de variable a permanecer siempre igual:

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

Godot intentará inferir tipos si escribe dos puntos, pero puedes omitirlo:

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

Actualmente puedes usar tres tipos de ... tipos:

  1. Integrados
  2. Clases principales y nodos (Object, Node, Area2D, Camera2D, etc.)
  3. Tu propio tipos, clases personalizadas. Observa la nueva funcionalidad class_name para registrar tipos en el editor.

Nota

No necesitas escribir sugerencias de tipo para constantes, ya que Godot lo establece automáticamente a partir del valor asignado. Pero aún puede hacerlo para aclarar la intención de su código.

Tipos personalizados

Puedes utilizar cualquier clase, incluidas sus clases personalizadas, como tipos. Hay dos formas de usarlos en scripts. El primer método es precargar el script que desea utilizar como un tipo en una constante:

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

El segundo método es usando la palabra clave class_name cuando lo creas. Para el ejemplo anterior, tu Rifle.gd se vería así:

extends Node2D
class_name Rifle

Si usas class_name, Godot registrará el tipo Rifle globalmente en el editor y lo podrás usar en cualquier parte, sin tener que precargarlo en una constante:

var my_rifle: Rifle

Conversión de variables (casting)

Casting es un concepto clave en lenguajes tipados, es la conversión de un valor de un tipo a otro.

Imagina un Enemy en tu juego, que extiende de Area2D. Y quieres que colisione con el Player, un KinematicBody2D con un script asociado llamado PlayerController. Usarás la señal on_body_entered para detectar la colisión. Con el código tipado, el cuerpo detectado será de un tipo genérico PhysicsBody2D y no tu PlayerController, en el callback de _on_body_entered.

Puedes comprobar si PhysicsBody2D es tu Jugador con la palabra clave de conversión as y usando : para forzar a la variable a usar este tipo. Esto hace que la variable se asuma como del tipo PlayerController:

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

    player.damage()

Como estamos lidiando con tipos personalizados, si el body no extiende PlayerControler, la variable ``Player será null. Podemos usar esto para saber si body es Player o no. También tendremos mejor autocompletado en la variable Player gracias a la conversión.

Nota

Si intentas convertir a un tipo integrado y falla, Godot disparará un error.

Líneas seguras

Puedes usar casting para asegurarte de tener líneas de código seguras. Las líneas seguras son una nueva herramienta de Godot 3.1 que indican cuando líneas ambiguas de código son de tipado seguro (type-safe). Puedes mezclar codigo dinámico y tipado y a veces Godot no tendrá información suficiente para indicar si la instrucción resultará o no en un error en tiempo de ejecución.

Esto sucede cuando obtienes un nodo hijo. Tomemos un temporizador (timer) como ejemplo: con código dinámico obtendrás el nodo con $Timer. GDScript soporta lo comunmente llamado duck-typing https://es.wikipedia.org/wiki/Duck_typing, así que mientras que tu temporizador es de tipo Timer, también es un Node y un Object, las dos clases de las cual extiende. Con GDScript dinámico no deberá importarte de qué tipo es el nodo mientras tenga los métodos que necesites llamar.

Puedes usar casting para decirle a Godot el tipo que esperas cuando obtienes un nodo: ($Timer as Timer), ($Player as KinematicBody2D), etc. Godot se asegurará de que el tipo es correcto y en ese caso, la línea se marcará de verde a la izquierda del editor de script.

Unsafe vs Safe Line

Línea no segura (línea 7) vs Línea segura (líneas 6 y 8)

Nota

Puedes apagar el chequeo de líneas seguras (safe lines) o cambiar el color en los ajustes del editor.

Define el tipo de retorno de una función con la flecha ->

Para definir el tipo de retorno de una función, escribe un guion y un símbolo mayor que -> después de su declaración, seguido del tipo de retorno:

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

El tipo void significa que la función no retorna nada. Puedes usar cualquier tipo como con las variables:

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

También puedes usar tus propios nodos 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

Tipado estático o dinámico: apégate a un estilo

GDScript de tipado estático y GDScript de tipado dinámico pueden coexistir en el mismo proyecto. Pero se recomienda seguir uno de los dos estilos para mantener la coherencia en el código base y para tus compañeros. Es más fácil para todos trabajar juntos si sigue las mismas pautas, y más rápido para leer y comprender el código de otras personas.

El código tipado lleva más tiempo para escribir, pero tiene el beneficio que se mencionó anteriormente. Aquí hay un ejemplo del mismo script vacío en un estilo dinámico:

extends Node


func _ready():
    pass


func _process(delta):
    pass

Y con tipado estático:

extends Node


func _ready() -> void:
    pass


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

Como puedes ver, se pueden usar tipos con métodos virtuales del motor. Los callbacks de señales, como cualquier otro método, también pueden usar tipos. Aquí hay una señal body_entered con tipado dinámico:

func _on_Area2D_body_entered(body):
    pass

Y el mismo callback con tipos especificados:

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

Eres libre de reemplazar, por ejemplo, el CollisionObject2D, con tu propio tipo, para establecer parámetros automáticamente:

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

    take_damage(bullet.damage)

La variable bullet puede contener cualquier CollisionObject2D pero nos aseguraremos de que sea nuestro Bullet, un nodo que creamos para nuestro proyecto. Si es cualquier otra cosa, como un Area2D o cualquier otro que no extienda de Bullet, la variable bullet será null.

Sistema de advertencias

Nota

La documentación sobre el sistema de advertencias de GDScript se ha movido a Sistema de advertencias de GDScript.

Casos en los que no puedes especificar tipos

Para concluir con esta introducción, cubriremos algunos de los casos donde no se puede usar tipado. Todos los ejemplos siguientes generarán errores.

No puedes usar Enums como tipos:

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

No puedes especificar tipos de miembros individuales de un arreglo. Esto te dará un error:

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

No puedes forzar la asignación de tipos en un bucle for, ya que cada elemento de for se ejecuta con un tipo ya definido para el bucle. Así que no puedes escribir:

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

Dos scripts no pueden depender uno del otro de manera cíclica:

# Player.gd

extends Area2D
class_name Player


var rifle: Rifle
# Rifle.gd

extends Area2D
class_name Rifle


var player: Player

Sumario

GDScript tipado es una herremienta poderosa. Disponible desde la versión 3.1 de Godot, ayuda a escribir código más estructurado, evitar errores comunes y crear sistemas más escalables. En el futuro, tipos estáticos permitirán mejorar el rendimiento gracias a optimizaciones del compilador.