Guide de style GDScript

Ce guide de style liste les standards pour écrire du GDScript élégant. Le but est d'encourager l'écriture de code propre et lisible ainsi que de promouvoir l'uniformité au travers des projets, les discussions et les tutoriels. Nous espérons que cela encouragera le développement d'outil d'auto-formatage.

Puisque GDScript ressemble à Python, ce guide est inspiré par le guide de style de programmation de Python PEP 8 .

Les guides de style ne sont pas conçus comme des livres de règles rigides. Parfois, il se peut que vous ne puissiez pas appliquer certaines des règles ci-dessous. Lorsque cela se produit, utilisez votre jugement et demandez à vos collègues développeurs de vous donner leur avis.

En général, garder votre code consistant dans vos projets et au sein de votre équipe est plus important que de suivre ce guide à la lettre.

Note

L'éditeur de script intégré dans Godot utilise beaucoup de ces standards par défaut. Laissez-le vous aider.

Voici un exemple de classe complet basé sur ces lignes directrices :

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")

Formatage

Encodage et caractères spéciaux

  • Utilisez le saut de ligne (LF) pour terminer les lignes, et non CRLF ou CR. (éditeur par défaut)
  • Utilisez un caractère de saut de ligne à la fin de chaque fichier. (éditeur par défaut)
  • Utilisez l'encodage UTF-8 sans indicateur d'ordre d'octet. (éditeur par défaut)
  • Utilisez Tabs au lieu d'espaces pour l'indentation. (éditeur par défaut)

Indentation

Chaque niveau d'indentation doit être supérieur d'une unité au bloc qui le contient.

Bon :

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

Mauvais :

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

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

Utiliser 2 niveaux d'indentations pour distinguer les suites de lignes des blocs de code réguliers.

Bon :

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

Mauvais :

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

Les exceptions à cette règle sont les tableaux, les dictionnaires et les énumérations. Utilisez un seul niveau d'indentation pour distinguer les lignes de continuation :

Bon :

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

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

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

Mauvais :

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

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

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

Virgule de fin

Utilisez une virgule de fin de ligne sur la dernière ligne des tableaux, dictionnaires et énumérations. Ceci a pour résultat un refactoring plus facile et de meilleures différences dans le contrôle de version car la dernière ligne n'a pas besoin d'être modifiée lors de l'ajout de nouveaux éléments.

Bon :

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

Mauvais :

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT
}

Les virgules de fin de ligne sont inutiles dans les listes en une ligne, alors ne les ajoutez pas dans ce cas.

Bon :

enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

Mauvais :

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

Lignes vides

Encadrer les fonctions et les définitions de classes par deux ligne vide :

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)

Utiliser une ligne vide à l'intérieur des fonctions pour séparer les sections logiques.

Longueur de la ligne

Gardez chaque ligne de code en dessous de 100 caractères.

Si vous le pouvez, essayez de ne pas dépasser 80 caractères par ligne. Cela permet de lire le code sur de petits écrans et avec deux scripts ouverts côte à côte dans un éditeur de texte externe. Par exemple, lorsqu'il s'agit d'une révision différentielle.

Une Instruction par ligne

Ne combinez jamais plusieurs instructions sur une même ligne. Non, les programmeurs C, pas avec une seule ligne d'instruction conditionnelle.

Bon :

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

if flag:
    print("flagged")

Mauvais :

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

if flag: print("flagged")

La seule exception à cette règle est l'opérateur ternaire :

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

Évitez les parenthèses inutiles

Évitez les parenthèses dans les expressions et les instructions conditionnelles. Sauf si elles sont nécessaires pour l'ordre des opérations, elles ne font que réduire la lisibilité.

Bon :

if is_colliding():
    queue_free()

Mauvais :

if (is_colliding()):
    queue_free()

Opérateurs booléens

Préférez les versions en anglais des opérateurs booléens, car elles sont les plus accessibles :

  • Utilisez and au lieu de &&.
  • Utilisez or au lieu de ||.

Vous pouvez également utiliser des parenthèses autour des opérateurs booléens pour éviter toute ambiguïté. Cela peut améliorer la lisibilité des longues expressions.

Bon :

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

Mauvais :

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

Espace de commentaires

Les commentaires normaux doivent commencer avec un espace, mais pas les commentaires qui désactivent du code. Cela aide à différencier les commentaires textes du code désactivé.

Bon :

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

Mauvais :

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

Note

Dans l'éditeur de script, pour basculer le code sélectionné en commenté, appuyez sur Ctrl + K. Cette fonctionnalité ajoute un symbole # au début des lignes sélectionnées.

Espace

Utiliser toujours un seul espace autour des opérateurs et après les virgules. Éviter les espaces supplémentaires dans les références de dictionnaires et les appels de fonctions.

Bon :

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

Mauvais :

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

Ne pas utiliser d'espaces pour aligner verticalement les expressions :

x        = 100
y        = 100
velocity = 500

Guillemets

Utilisez des guillemets doubles dans la mesure du possible, sauf si les guillemets simples peuvent réduire le nombre de caractères à échapper dans une chaîne de caractères donnée. Voir l'exemple ci-dessous :

# 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\"")

Conventions de nommage

Ces conventions de nommage suivent le style du moteur Godot. Si vous ne les respectez pas, votre code se détachera des conventions de nommage existantes, ce qui entraînera un code incohérent.

Nom de fichier

Utilisez snake_case pour les noms de fichiers. Pour les classes nommées, convertissez le nom de la classe de PascalCase en 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

Cela correspond à la façon dont les fichiers C++ sont nommés dans le code source de Godot. Cela permet également d'éviter les problèmes de sensibilité à la casse qui peuvent survenir lors de l'exportation d'un projet de Windows vers d'autres plateformes.

Classes et Noeuds

Utiliser le PascalCase pour les noms des classes et des nœuds :

extends KinematicBody

Utiliser aussi le PascalCase lorsque vous chargez une classe dans une constante ou une variable :

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

Fonctions et Variables

Utiliser le snake_case pour nommer les fonctions et variables :

var particle_effect
func load_level():

Préfixer un seul trait de soulignement (_) aux méthodes virtuelles que l'usager doit redéfinir, les fonctions privées, et les variables privées :

var _counter = 0
func _recalculate_path():

Signaux

Utiliser le passé pour nommer les signaux :

signal door_opened
signal score_changed

Constantes et énumérations

Écrivez les constantes avec CONSTANT_CASE, c'est-à-dire en majuscules avec un trait de soulignement (_) pour séparer les mots :

const MAX_SPEED = 200

Utilisez PascalCase pour les énumération(enum) names et CONSTANT_CASE pour leurs membres, car ce sont des constantes :

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

Ordre du code

Cette première section porte sur l'ordre du code. Pour le formatage, voir Formatage. Pour les conventions de nommage, voir Conventions de nommage.

Nous suggérons d'organiser le code GDScript de cette manière :

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

Nous avons optimisé l'ordre pour rendre le code plus simple à lire de haut en bas, afin d'aider les développeurs à comprendre comment le code fonctionne la première fois qu'ils le lisent, et pour éviter les erreurs liées à l'ordre de déclaration des variables.

Cet ordre du code suit quatre règles générales :

  1. Les propriétés et signaux viennent en premier, suivis des méthodes.
  2. Publique vient avant privé.
  3. Les rappels(callbacks) virtuels précèdent l'interface de la classe.
  4. Les fonctions de construction et d'initialisation de l'objet, _init et _ready, viennent avant les fonctions qui modifient l'objet à l'exécution.

Déclaration de classe

Si le code est destiné à être exécuté dans l'éditeur, utilisez le mot clé tool à la première ligne du script.

Ajoutez class_name si nécessaire. Vous pouvez transformer un fichier GDScript en un type global dans votre projet à l'aide de cette fonctionnalité. Pour plus d'informations, voir Bases de GDScript.

Ensuite, ajoutez le mot-clé extends si la classe étend un type intégré.

Après cela, vous devriez avoir la docstring optionnelle de la classe en commentaire. Vous pouvez l'utiliser pour expliquer le rôle de votre classe aux membres de votre équipe, comment elle fonctionne et comment les autres développeurs devraient l'utiliser, par exemple.

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

Signaux et propriétés

Écrivez les déclarations de signaux, suivies des propriétés, c'est-à-dire les variables membres, après la docstring.

Les énumérations doivent venir après les signaux, car vous pouvez les utiliser comme indices d’exportation pour les autres propriétés.

Ensuite, écrivez les constantes, les variables exportées puis les variables publiques, privées et onready, dans cet ordre.

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")

Note

Le compilateur GDScript évalue les variables marquées onready juste avant le rappel _ready. Vous pouvez utiliser cela pour mettre en cache les dépendances des nœuds, c'est-à-dire pour obtenir des nœuds enfants dans la scène sur laquelle votre classe s'appuie. C'est ce que montre l'exemple ci-dessus.

Variables membres

Ne déclarez pas les variables membres si elles ne sont utilisées que localement dans une méthode, car cela rend le code plus difficile à suivre. Déclarez-les plutôt comme des variables locales dans le corps de la méthode.

Variables locales

Déclarez les variables locales le plus près possible de leur première utilisation. Il est ainsi plus facile de suivre le code, sans avoir à trop le faire défiler pour retrouver l'endroit où la variable a été déclarée.

Méthodes et fonctions statiques

Après les propriétés de la classe viennent les méthodes.

Commencez avec la méthode de rappel _init (), que le moteur appellera lors de la création de l'objet en mémoire. Continuez avec la méthode de rappel _ready (), que Godot appelle quand il ajoute un nœud à l'arbre des scènes.

These functions should come first because they show how the object is initialized.

D'autres méthodes de rappel virtuelles intégrées, comme _unhandled_input() et _physics_process, doivent venir ensuite. Celles-ci contrôlent la boucle principale de l'objet et les interactions avec le moteur de jeu.

Le reste de l'interface de la classe, les méthodes publiques et privées, viennent ensuite dans cet ordre.

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")

Typage statique

Depuis Godot 3.1, GDScript supporte le typage statique optionnel.

Indices de types

Ajoutez deux-points après le nom d'une variable, sans espace, et le compilateur GDScript en déduira le type de la variable quand cela est possible.

Bon :

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

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

Mauvais :

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

Quand vous laissez le compilateur déduire le type, écrivez les deux-points et le signe égale attaché : :=.

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

Ajoutez un espace de chaque côté de la flèche indiquant le type de retour dans l'en-tête de vos fonctions.

func heal(amount: int) -> void: