Guía de estilo de GDScript

Esta guía de estilo enumera las convenciones para escribir un GDscript elegante. La meta es motivar la escritura de un código limpio, legible y promover la consistencia entre proyectos, discusiones y tutoriales. Esperamos que esto también motive al desarrollo de herramientas de autoformateo.

Como GDScript es similar a Python, esta guía ha sido inspirada por la guía de estilo de programación PEP 8.

Las guías de estilos no están hechas con la intención de ser tratadas como reglamento estricto. Algunas veces no serás capaz de utilizar algunos de los lineamientos indicados, cuando eso suceda utiliza tu mejor criterio y busca la opinión de otros desarrolladores.

En general, mantener tu código consistente en tus proyectos y entre todos los de tu equipo es más importante que seguir esta guía al pié de la letra.

Nota

El editor de scripts de Godot usa muchas de estas convenciones por defecto. Deja que te ayude.

Aquí tenemos un ejemplo completo de una clase basada en esos lineamientos:

class_name StateMachine
extends Node
# Hierarchical State machine for the player.
# Initializes states and delegates engine callbacks
# (_physics_process, _unhandled_input) to the state.


signal state_changed(previous, new)

export var initial_state = NodePath()
var is_active = true setget set_is_active

onready var _state = get_node(initial_state) setget set_state
onready var _state_name = _state.name


func _init():
    add_to_group("state_machine")


func _ready():
    connect("state_changed", self, "_on_state_changed")
    _state.enter()


func _unhandled_input(event):
    _state.unhandled_input(event)


func _physics_process(delta):
    _state.physics_process(delta)


func transition_to(target_state_path, msg={}):
    if not has_node(target_state_path):
        return

    var target_state = get_node(target_state_path)
    assert(target_state.is_composite == false)

    _state.exit()
    self._state = target_state
    _state.enter(msg)
    Events.emit_signal("player_state_changed", _state.name)


func set_is_active(value):
    is_active = value
    set_physics_process(value)
    set_process_unhandled_input(value)
    set_block_signals(not value)


func set_state(value):
    _state = value
    _state_name = _state.name


func _on_state_changed(previous, new):
    print("state changed")
    emit_signal("state_changed")

Formateando

Codificación y caracteres especiales

  • Utiliza caracteres de salto de línea (LF) para separar líneas, no CRLF o CR. (por defecto del editor)

  • Utiliza un carácter de salto de línea al final de cada archivo. (por defecto del editor)

  • Usa la codificación UTF-8 sin Marca de orden de bytes. *( por defecto del editor) *

  • Usa Tabs en lugar de espacios para la indentación. *(por defecto del editor) *

Indentación

Cada nivel de la Indentación deberá uno mayor que el bloque que lo contiene.

Bien:

for i in range(10):
    print("hello")

Mal:

for i in range(10):
  print("hello")

for i in range(10):
        print("hello")

Use 2 niveles de Indentación para distinguir las lineas de continuación de bloques regulares de código.

Bien:

effect.interpolate_property(sprite, "transform/scale",
            sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
            Tween.TRANS_QUAD, Tween.EASE_OUT)

Mal:

effect.interpolate_property(sprite, "transform/scale",
    sprite.get_scale(), Vector2(2.0, 2.0), 0.3,
    Tween.TRANS_QUAD, Tween.EASE_OUT)

Las excepciones a esta regla son los arrays, diccionarios y enumeraciones. Usa una sangría simple para distinguir líneas de continuación:

Bien:

var party = [
    "Godot",
    "Godette",
    "Steve",
]

var character_dir = {
    "Name": "Bob",
    "Age": 27,
    "Job": "Mechanic",
}

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT,
}

Mal:

var party = [
        "Godot",
        "Godette",
        "Steve",
]

var character_dir = {
        "Name": "Bob",
        "Age": 27,
        "Job": "Mechanic",
}

enum Tiles {
        TILE_BRICK,
        TILE_FLOOR,
        TILE_SPIKE,
        TILE_TELEPORT,
}

Coma final

Utiliza una coma final en la última línea de arrays, diccionarios y enumeraciones. Esto resulta en una refactorización más sencilla y mejores indicaciones de diferencias (diffs) al trabajar con control de versions, ya que la última línea no necesita ser modificada cuando se agregan nuevos elementos.

Bien:

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT,
}

Mal:

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT
}

Las comas de finalización no son necesarias en listas de una sola línea, así que no las agregues en ese caso.

Bien:

enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

Mal:

enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT,}

Lineas en blanco

Envuelve las funciones y definiciones de clases con dos lineas vacías:

func heal(amount):
    health += amount
    health = min(health, max_health)
    emit_signal("health_changed", health)


func take_damage(amount, effect=null):
    health -= amount
    health = max(0, health)
    emit_signal("health_changed", health)

use una linea blanca dentro de las funciones para separar secciones lógicas.

Longitud de línea

Mantiene líneas de código individuales por debajo de los 100 caracteres.

Si puedes, trata de mantener las líneas debajo de 80 caracteres. Esto ayuda a que se pueda leer el código en pantallas pequeñas y posibilita tener abiertos dos scripts lado a lado en un editor de texto externo. Por ejemplo, cuando se está mirando una revisión diferencial.

Una declaración/instrucción por linea

Nunca combine múltiples declaraciones en una única línea. No, programadores de C, ni siquiera declaraciones condicionales en una única línea.

Bien:

if position.x > width:
    position.x = 0

if flag:
    print("flagged")

Mal:

if position.x > width: position.x = 0

if flag: print("flagged")

La única excepción a esta regla es el operador ternario:

next_state = "fall" if not is_on_floor() else "idle"

Evita paréntesis innecesarios

Evite paréntesis en expresiones y condicionales. A menos que sean necesarios para el orden de las operaciones, solo reducen la legibilidad.

Bien:

if is_colliding():
    queue_free()

Mal:

if (is_colliding()):
    queue_free()

Operador booleanos

Usa la versión en inglés de operadores booleanos, ya que son más accesibles:

  • Usa and en lugar de &&.

  • Usa or en lugar de ||.

También puedes usar paréntesis alrededor de operadores booleanos para evitar cualquier ambigüedad. Esto hace que expresiones largas sean más fáciles de leer.

Bien:

if (foo and bar) or baz:
    print("condition is true")

Mal:

if foo && bar || baz:
    print("condition is true")

Espaciado de comentarios

Comentarios normales deberían comenzar con un espacio, pero no el código comentado. Esto ayuda a diferenciar texto comentado de código deshabilitado.

Bien:

# This is a comment.
#print("This is disabled code")

Mal:

#This is a comment.
# print("This is disabled code")

Nota

En el editor de scripts, para cambiar estado de comentario en el código, presiona Ctrl + K. Esta característica agrega un signo # al principio de las líneas seleccionadas.

Espacio en blanco

Utiliza siempre un espacio entre los operadores y después de las comas. Evita los espacios adicionales en las referencias de los diccionarios y las llamadas de función.

Bien:

position.x = 5
position.y = target_position.y + 10
dict["key"] = 5
my_array = [4, 5, 6]
print("foo")

Mal:

position.x=5
position.y = mpos.y+10
dict ["key"] = 5
myarray = [4,5,6]
print ("foo")

No uses espacios para alinear la verticalidad de las expresiones:

x        = 100
y        = 100
velocity = 500

Comillas

Utiliza comillas dobles a menos que las comillas simple hagan positble escapar menos caracteres en una cadena dada. Revisa los ejemplos a continuación:

# Normal string.
print("hello world")

# Use double quotes as usual to avoid escapes.
print("hello 'world'")

# Use single quotes as an exception to the rule to avoid escapes.
print('hello "world"')

# Both quote styles would require 2 escapes; prefer double quotes if it's a tie.
print("'hello' \"world\"")

Numeros

No omitas el cero inicial o final en los números reales. De lo contrario, esto los hace menos legibles y más difíciles de distinguir de los números enteros a simple vista.

Bien:

var float_number = 0.234
var other_float_number = 13.0

Mal:

var float_number = .234
var other_float_number = 13.

Use minúsculas para las letras en números hexadecimales, ya que su menor altura hace que el número sea más fácil de leer.

Bien:

var hex_number = 0xfb8c0b

Mal:

var hex_number = 0xFB8C0B

Aprovechen los subrayados de GDScript en los literales para hacer más legibles los grandes números.

Bien:

var large_number = 1_234_567_890
var large_hex_number = 0xffff_f8f8_0000
var large_bin_number = 0b1101_0010_1010
# Numbers lower than 1000000 generally don't need separators.
var small_number = 12345

Mal:

var large_number = 1234567890
var large_hex_number = 0xfffff8f80000
var large_bin_number = 0b110100101010
# Numbers lower than 1000000 generally don't need separators.
var small_number = 12_345

Convenciones para la definición de nombres

Estas convenciones de nombres siguen el estilo de Godot Engine. Romperlas hará que tu código choque con las convenciones de nomenclaturas incorporadas, lo que crea un código inconsistente.

Nombres de archivo

Utiliza snake_case para nombres de archivos. Para clases con nombre, convierte el nombre de clase en PascalCase a snake_case:

# This file should be saved as `weapon.gd`.
class_name Weapon
extends Node
# This file should be saved as `yaml_parser.gd`.
class_name YAMLParser
extends Object

Esto es consistente con cómo son nombrados los archivos en el código fuente C++ de Godot. Esto también evita problema de sensibilidad a mayúsculas que pueden surgir cuando se exporta un proyecto desde Windows a otras plataformas.

Clases y Nodos

Utiliza PascalCase para nombres de clases y nodos:

extends KinematicBody

También usa el PascalCase al cargar una clase en una constante o una variable:

const Weapon = preload("res://weapon.gd")

Funciones y Variables

Usa snake_case para nombrar funciones y variables:

var particle_effect
func load_level():

Utilice un solo símbolo de subrayado (_) para los métodos virtuales que el usuario debe sobreescribir, funciones privadas y variables privadas:

var _counter = 0
func _recalculate_path():

Señales

Usa el tiempo pasado para nombrar señales:

signal door_opened
signal score_changed

Constantes y enumerables

Escribe constantes en CONSTANT_CASE, todas en mayusculas con el símbolo de subrayado (_) para separar palabras:

const MAX_SPEED = 200

Usa PascalCase para nombres de enums y CONSTANT_CASE para sus miembros, ya que son constantes:

enum Element {
    EARTH,
    WATER,
    AIR,
    FIRE,
}

Orden de código

Esta sección inicial se enfoca en el orden del código. Para el formato, ver Formateando. Para convenciones de nombre, ver Convenciones para la definición de nombres.

Sugerimos organizar el código GDScript de este modo:

01. tool
02. class_name
03. extends
04. # docstring

05. signals
06. enums
07. constants
08. exported variables
09. public variables
10. private variables
11. onready variables

12. optional built-in virtual _init method
13. built-in virtual _ready method
14. remaining built-in virtual methods
15. public methods
16. private methods

Optimizamos el orden para hacer más fácil leer el código desde arriba hacia abajo, para ayudar a que los desarrolladores que leen el código por primera vez entiendan cómo funciona y para evitar errores vinculados al orden de declaración de variables.

Este orden de código sigue cuatros reglas:

  1. Propiedades y señales vienen primero, seguidas de métodos.

  2. Público viene antes de privado.

  3. Callbacks virtuales vienen antes de la interfaz de clase.

  4. Las funciones de construcción e inicialización de objetos, _init and _ready, vienen antes de funciones que modifican el objeto en tiempo de ejecución.

Declaración de la clase

Si este código debe ser ejecutado en el editor, coloca la palabra clave tool en la primera línea del script.

Seguido de class_name si es necesario. Puedes convertir un archivo GDScript en un tupo global de tu proyecto usando esta caracterísitica. Para más información, ver Bases de GDScript.

Luego, agrega la palabra clave extends si la clase extiende de algún tipo integrado.

Seguido a esto, deberás tener la docstring opcional de clase como comentario. Puedes usar esto para explicar el rol de tu clase a tus compañeros de equipo, cómo trabaja, cómo otros desarrolladores deberán usarla, por ejemplo.

class_name MyNode
extends Node
# A brief description of the class's role and functionality.
# Longer description.

Señales y propiedades

Escribe la declaración de señales, seguida por propiedades, es decir, las variables miembro después de la docstring.

Los enums deberán venir después de las señales, ya que puedes usarlos como "export hints" para otras propiedades.

Luego, escribe constantes, variables exportadas, públicas, privades y onready, en ese orden.

signal spawn_player(position)

enum Jobs {KNIGHT, WIZARD, ROGUE, HEALER, SHAMAN}

const MAX_LIVES = 3

export(Jobs) var job = Jobs.KNIGHT
export var max_health = 50
export var attack = 5

var health = max_health setget set_health

var _speed = 300.0

onready var sword = get_node("Sword")
onready var gun = get_node("Gun")

Nota

El compilador GDScript evalúa variables onready justo antes del callback _ready. Puedes usar esto para guardar referencias a dependencias de nodos, es decir, para obtener nodos en la escena de lo que tu clase depende. Esto es lo que muestra el ejemplo anterior.

Varaibles miembro

No declares variables miembro si sólo serán usadas localmente en un método, esto hace el código más difícil de seguir. En lugar de esto declaralas como varaibles locales en el cuerpo del método.

Variables locales

Declara las variables locales tan cerca como puedas de su primer uso. Esto hace que sea más fácil de seguir el código sin tener que desplazar el código demasiado para encontrar dónde fue declarada la variable.

Métodos y funciones estáticas

Después las propiedades de la clase vienen los métodos.

Comienza por el método callback _init() que el motor llamará cuando se crea el objeto en memoria. Seguido del callback _ready(), el que Godot llama cuando un nodo es agregado al árbol de escena.

Estas funciones deben ser lo primero porque muestran cómo se inicializa el objeto.

Otros callbacks virtuales integrados, como _unhandled_input() y _physics_process, van después. Estos controlan el bucle principal del objeto y las interacciones con el motor de juegos.

El resto de la interfaz de la clase, métodos públicos y privados, vienen después en ese orden.

func _init():
    add_to_group("state_machine")


func _ready():
    connect("state_changed", self, "_on_state_changed")
    _state.enter()


func _unhandled_input(event):
    _state.unhandled_input(event)


func transition_to(target_state_path, msg={}):
    if not has_node(target_state_path):
        return

    var target_state = get_node(target_state_path)
    assert(target_state.is_composite == false)

    _state.exit()
    self._state = target_state
    _state.enter(msg)
    Events.emit_signal("player_state_changed", _state.name)


func _on_state_changed(previous, new):
    print("state changed")
    emit_signal("state_changed")

Tipado estáticas

Desde Godot 3.1, GDScript soporta tipado estático opcional.

Tipos declarados

Para declarar el tipo de una variable, usa <variable>: <type>:

var health: int = 0

Para declarar el tipo de retorno de una función, usa -> <tipo>:

func heal(amount: int) -> void:

Tipos inferidos

En la mayoría de los casos puedes dejar que el compilador infiera el tipo usando :=:

var health := 0  # The compiler will use the int type.

Sin embargo, en unos pocos casos en los que falta el contexto, el compilador vuelve al tipo de retorno de la función. Por ejemplo, get_node() no puede inferir un tipo a menos que la escena o el archivo del nodo se cargue en la memoria. En este caso, debe establecer el tipo explícitamente.

Bien:

onready var health_bar: ProgressBar = get_node("UI/LifeBar")

Mal:

# The compiler can't infer the exact type and will use Node
# instead of ProgressBar.
onready var health_bar := get_node("UI/LifeBar")