GDScript Stil-Richtlinien

Diese Stil-Richtlinien listen Konventionen zum Schreiben von elegantem GDScript auf. Das Ziel ist Sie dazu zu ermutigen, sauberen und lesbaren Code zu schreiben und die Konsistenz aller Projekte, Diskussionen und Anleitungen zu fördern. Hoffentlich wird dies auch die Entwicklung von Auto-Formatierungswerkzeugen ankurbeln.

Da GDScript Python ähnelt, ist diese Anleitung inspiriert von Python's PEP 8 Programmier-Stil-Richtlinien.

Stil-Richtlinien sind nicht als harte Regelbücher gedacht. Manchmal können Sie einige der folgenden Richtlinien möglicherweise nicht anwenden. In diesem Fall urteilen Sie selbst was wohl am besten wäre und fragen Sie andere Entwickler nach Erkenntnissen.

Im Allgemeinen ist es wichtiger, Ihren Code in Ihren Projekten und in Ihrem Team konsistent zu halten, als diesem Leitfaden blind zu folgen.

Bemerkung

Godot's integrierter Skripteditor verwendet standardmäßig einige dieser Konventionen.

Hier ist ein vollständiges Klassenbeispiel basierend auf diesen Richtlinien:

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

Formatierung

Kodierung und Sonderzeichen

  • Benutzen Sie Line Feed (LF) für Zeilenumbrüche, nicht CRLF oder CR. (Standardeinstellung im Editor)
  • Benutzen Sie ein Line Feed am Ende jeder Datei. (Standardeinstellung im Editor)
  • Verwenden Sie die UTF-8-Codierung ohne ein byte order mark. (Editor Standard)
  • Benutzen Sie Tabs anstelle von Leerzeichen für Einrückungen. (Standardeinstellung im Editor)

Einrückungen

Jede Einrückungsebene sollte um eins größer sein als die des umgebenden Blocks.

Gut:

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

Schlecht:

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

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

Rücke 2 Ebenen ein, um fortgesetzte Zeilen von regulären Codeblöcken zu unterscheiden.

Gut:

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

Schlecht:

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

Ausnahmen von dieser Regel sind Arrays, Wörterbücher und Aufzählungen. Verwenden Sie eine einzelne Einrückungsstufe, um fortgesetzte Zeilen zu unterscheiden:

Gut:

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

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

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

Schlecht:

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

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

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

nachfolgendes Komma

Verwenden Sie in Arrays, Wörterbüchern und Aufzählungen in der letzten Zeile ein nachfolgendes Komma. Dies führt zu einem einfacheren Refactoring und besseren Unterschieden in der Versionskontrolle, da die letzte Zeile beim Hinzufügen neuer Elemente nicht geändert werden muss.

Gut:

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

Schlecht:

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT
}

Nachfolgende Kommas sind in einzeiligen Listen nicht erforderlich. Fügen Sie sie in diesem Fall also nicht hinzu.

Gut:

enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

Schlecht:

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

Leere Zeilen

Vor und nach einer Funktion- bzw. Klassendefinition sollten zwei Leerzeilen sein:

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)

Verwende eine Leerzeile um logische Abschnitte innerhalb einer Funktion zu separieren.

Zeilenlänge

Halten Sie einzelne Codezeilen unter 100 Zeichen.

Wenn Sie können, versuchen Sie, Zeilen unter 80 Zeichen zu halten. Dies hilft beim Lesen des Codes auf kleinen Displays und mit zwei nebeneinander geöffneten Skripten in einem externen Texteditor. Zum Beispiel bei der Betrachtung einer Datei in unterschiedlichen Versionen (Differenzkontrolle).

Eine Anweisung pro Zeile

Schreibe niemals mehrere Anweisungen in einer Zeile. Nein, liebe C Programmierer, auch keine einzeiligen bedingten Anweisungen.

Gut:

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

if flag:
    print("flagged")

Schlecht:

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

if flag: print("flagged")

Die einzige Ausnahme dieser Regel ist der Tenär Operator:

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

Vermeide unnötige Klammern

Vermeide Klammern in Ausdrücken und Bedingungen. Außer um logische Reihenfolgen zu bestimmen, sie verringern nur die Lesbarkeit.

Gut:

if is_colliding():
    queue_free()

Schlecht:

if (is_colliding()):
    queue_free()

Boolesche Operatoren

Benutze wenn möglich die englische Version der Booleschen Operatoren, da diese verständlicher sind:

  • nutze and anstelle von &&.
  • nutze or anstelle von ||.

Es können auch Klammern um Boolesche Operatoren gesetzt werden um Unklarheiten zu beseitigen. Hierdurch werden lange Ausdrücke lesbarer.

Gut:

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

Schlecht:

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

Leerzeichen bei Kommentaren

Kommentare sollten mit einem Leerzeichen beginnen, aber kein Code der auskommentiert wird (inaktiv). Dies hilft Kommentare von inaktivem Code zu unterscheiden.

Gut:

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

Schlecht:

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

Bemerkung

Drücken Sie im Skripteditor Ctrl + K, um den ausgewählten Code auszukommentieren. Diese Funktion fügt am Anfang der ausgewählten Zeilen ein einzelnes # Zeichen hinzu.

Leerzeichen

Setze immer ein Leerzeichen um Operatoren und nach Kommas. Vermeide zusätzliche Leerzeichen in Dictionary-Referenzen oder Funktionsaufrufen.

Gut:

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

Schlecht:

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

Benutzen Sie keine Leerzeichen um Ausdrücke vertikal auszurichten (um "Spalten" zu erzeugen.):

x        = 100
y        = 100
velocity = 500

Anführungszeichen

Benutze doppelte Anführungszeichen, es sei denn, einfache Anführungszeichen erzeugen eine kürzere Zeichenfolge (keine Escape-Sequenzen). Hier ein Beispiel:

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

Nummern

Lassen Sie die führende oder nachfolgende Null in Gleitkommazahlen nicht weg. Andernfalls sind sie schwerer lesbar und auf einen Blick schwerer von Ganzzahlen zu unterscheiden.

Gut:

var float_number = 0.234
var other_float_number = 13.0

Schlecht:

var float_number = .234
var other_float_number = 13.

Verwenden Sie Kleinbuchstaben für Buchstaben in hexadezimalen Zahlen, da ihre geringere Höhe die Lesbarkeit der Zahl erleichtert.

Gut:

var hex_number = 0xfb8c0b

Schlecht:

var hex_number = 0xFB8C0B

Nutzen Sie die Unterstriche von GDScript in Literalen, damit große Zahlen lesbarer sind.

Gut:

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

Schlecht:

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

Benennungsrichtlinien

Diese Benennungsrichtlinien folgen dem Godot Engine Style. Bei Nichtbeachtung wird Ihr Code mit den eingebauten Bennennungsrichtlinien kollidieren, was zu inkonsistentem Code führt.

Dateinamen

Benutze einen Unterstrich für Dateinamen. Für Klassennamen (Wortanfänge groß) füge den Unterstrich ein (wandle PascalCase in 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

Dies steht im Einklang mit der Benennung von C++ Dateien im Godots Quellcode. Dadurch werden auch Probleme mit der Groß- und Kleinschreibung vermieden, die beim Exportieren eines Projekts von Windows auf andere Plattformen auftreten können.

Klassen und Nodes

Verwenden Sie PascalCase für Klassen- und Node-Namen:

extends KinematicBody

Außerdem beim abspeichern einer Klasse in eine Konstante oder Variable:

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

Funktionen und Variablen

Nutze snake_case um Funktionen und Variablen zu benennen:

var particle_effect
func load_level():

Stellen Sie einen einzelnen Unterstrich (_) vor virtueller Methoden, Funktionen die der Benutzer überschreiben muss, privaten Funktionen und privaten Variablen:

var _counter = 0
func _recalculate_path():

Signale

Nutze die Vergangenheitsform um Signale zu benennen:

signal door_opened
signal score_changed

Konstanten und Aufzählungen

Verwende CONSTANT_CASE, alles in Großbuchstaben, mit einem Unterstrich zur Worttrennung: const MAX_SPEED = 200:

const MAX_SPEED = 200

Verwenden Sie PascalCase für Aufzählungsnamen und CONSTANT _CASE für deren Mitglieder, da es sich um Konstanten handelt:

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

Code Reihenfolge

Dieser erste Abschnitt geht auf die Reihenfolge der Befehle im Code ein. Bzgl. Formatierungen siehe Formatierung. For naming conventions, see Benennungsrichtlinien.

Wir empfehlen GDScript Code auf diese Art zu organisieren:

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

Wir haben die Reihenfolge optimiert, um das Lesen des Codes von oben nach unten zu vereinfachen, Entwicklern das erste Lesen des Codes zu erleichtern und Fehler im Zusammenhang mit der Reihenfolge der Variablendeklarationen zu vermeiden.

Diese Code-Reihenfolge folgt vier Faustregeln:

  1. Eigenschaften und Signale stehen an erster Stelle, gefolgt von Methoden.
  2. Public kommt vor Private.
  3. Virtuelle Rückrufe kommen vor der Schnittstelle der Klasse.
  4. Die Konstruktions- und Initialisierungsfunktionen des Objekts, _init` und` _ready`, stehen vor Funktionen, die das Objekt zur Laufzeit ändern.

Klassen deklarieren

Wenn der Code im Editor ausgeführt werden soll, platzieren Sie das Schlüsselwort tool in der ersten Zeile des Skripts.

Geben Sie ggf. den Klassennamen ein. Mit dieser Funktion können Sie eine GDScript-Datei in Ihrem Projekt in einen globalen Typ umwandeln. Weitere Informationen finden Sie unter: ref: doc_gdscript.

Fügen Sie dann das Schlüsselwort extens hinzu, wenn die Klasse einen integrierten Typ erweitert.

Anschließend sollten Sie die optionale Dokumentzeichenfolge der Klasse als Kommentar haben. Sie können dies verwenden, um Ihren Teamkollegen die Rolle Ihrer Klasse zu erklären, wie sie funktioniert und wie andere Entwickler sie beispielsweise verwenden sollten.

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

Signale und Eigenschaften

Schreiben Sie Signaldeklarationen, gefolgt von Eigenschaften, das heißt, Mitgliedsvariablen nach der Dokumentzeichenfolge.

Aufzählungen sollten nach Signalen kommen, da Sie sie als Exporthinweise für andere Eigenschaften verwenden können.

Dann geht es in dieser Reihenfolge weiter: Konstanten schreiben , exportierte Variablen, public, private und onready Variablen.

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

Bemerkung

Der GDScript-Compiler wertet onready Variablen unmittelbar vor dem Rückruf _ready aus. Sie können dies verwenden, um Node-Abhängigkeiten zwischenzuspeichern, das heißt um untergeordnete Nodes in der Szene abzurufen, auf die sich Ihre Klasse bezieht. Dies zeigt das obige Beispiel.

Mitgliedsvariablen

Deklarieren Sie keine Mitgliedsvariablen, wenn sie nur lokal in einer Methode verwendet werden, da dies das Lesen des Codes erschwert. Deklarieren Sie sie stattdessen als lokale Variablen in der Methode.

Lokale Variablen

Deklarieren Sie lokale Variablen so nah wie möglich am Platz ihrer ersten Verwendung. Dies erleichtert das Lesen des Codes, ohne zu viel scrollen zu müssen, um herauszufinden, wo die Variable deklariert wurde.

Methoden und statische Funktionen

Nach den Klassen-Eigenschaften kommen die Methoden.

Beginnen Sie mit der Rückrufmethode _init(), die die Engine beim Erstellen des Objekts im Speicher aufruft. Folgen Sie dem Rückruf _ready() den Godot aufruft, wenn er dem Szenenbaum ein Node hinzufügt.

Diese Funktionen sollten an erster Stelle stehen, da sie zeigen, wie das Objekt initialisiert wird.

Andere integrierte virtuelle Rückrufe wie "_unhandled_input()" und "_physics_process" "sollten als nächstes folgen. Diese steuern die Hauptschleife des Objekts und die Interaktionen mit der Spiel-Engine.

Der Rest der Schnittstelle der Klasse, öffentliche und private Methoden, folgt danach in dieser Reihenfolge.

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

Statische Typisierung

Seit Godot 3.1 unterstützt GDScript: ref: optionale statische Typisierung <doc_gdscript_static_typing>.

Declared types

To declare a variable's type, use <variable>: <type>:

var health: int = 0

To declare the return type of a function, use -> <type>:

func heal(amount: int) -> void:

Inferred types

In most cases you can let the compiler infer the type, using :=:

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

However, in a few cases when context is missing, the compiler falls back to the function's return type. For example, get_node() cannot infer a type unless the scene or file of the node is loaded in memory. In this case, you should set the type explicitly.

Gut:

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

Schlecht:

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