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)
  • Utilice la codificación UTF-8 sin byte order mark. (por defecto del editor)
  • Utilice tabuladores 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 arreglos, 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 arreglos, 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\"")

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`.
extends Node
class_name Weapon
# This file should be saved as `yaml_parser.gd`.
extends Object
class_name YAMLParser

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 utilice PascalCase cuando se carga una clase en una constante o 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.

Esas funciones van primero porque muestran cómo es inicializado 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.

Indicio/hint de tipo

Coloca los dos puntos inmediatamente después del nombre de la variable, sin espacios, y deja que el compilador infiera el tipo de la variable cuando sea posible.

Bien:

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

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

Mal:

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

Cuando dejes que el compilador infiera el tipo, escribe los dos puntos y el signo igual juntos: :=.

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

Agrega un espacio a ambos lados de la llave que indica el tipo de retorno cuando definas funciones.

func heal(amount: int) -> void: