GDScript 스타일 가이드

여기에 있는 스타일 가이드는 GDScript를 아름답게 작성하도록 하는 관례를 나열했습니다. 이 스타일 가이드의 목적은 프로젝트 사이, 사람들의 토론 사이, 튜토리얼 사이에서 깨끗하고 읽기 쉬운 코드를 작성함과 동시에 일관성을 유지하도록 하는 것입니다. 이 기능이 자동 서식 도구의 개발에 도움이 되길 바랍니다.

GDScript가 Python과 밀접하기 때문에, 이 가이드는 Python의 PEP 8 프로그래밍 스타일 가이드에서 영감을 얻었습니다.

스타일 가이드는 빡빡한 법전이 아닙니다. 아직은 아래의 가이드라인을 적용하지 못할 수도 있습니다. 그렇다 하더라도, 최선의 판단으로 동료 개발자에게 통찰력을 갖도록 요청하세요.

일반적으로 프로젝트와 팀 내에서 코드를 일관적으로 유지하는 것이 아래의 가이드보다 더 중요합니다.

주석

Godot의 내장 스크립트 편집기는 기본적으로 많은 규정을 사용합니다. 그것이 당신을 돕도록 하십시오.

이 가이드라인을 기반으로 한 전체 클래스 예제입니다:

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

서식(Formatting)

인코딩과 특수 문자

  • 줄 바꿈을 위해 라인 피드 (LF) 문자를 사용합니다. CRLF나 CR은 사용하지 않습니다 (편집기 기본 설정)
  • 각 파일의 끝에 하나의 라인 피드 문자를 사용합니다. (편집기 기본 설정)
  • 바이트 순서 표식 없이 UTF-8 인코딩을 사용합니다. (편집기 기본 설정)
  • 들여쓰기로 스페이스바 대신 Tab(탭) 키를 사용합니다. (편집기 기본 설정)

들여쓰기(Indentation)

들여쓰기 너비는 블록 바깥보다 한 칸 더 커야 됩니다.

좋음:

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

나쁨:

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

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

정규 코드 블록과 이어지는 줄을 구분하기 위해 2 칸 들여쓰기를 사용하세요.

좋음:

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

나쁨:

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

이 규칙의 예외는 배열, 딕셔너리, 열거형입니다. 연속 선을 구분하려면 한 칸 들여쓰기를 사용하세요:

좋음:

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

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

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

나쁨:

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

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

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

쉼표 매달기(Trailing comma)

배열, 딕셔너리, 열거형의 마지막 줄에 쉼표 매달기를 사용하세요. 이렇게 하면 새 요소를 추가해도 마지막 줄을 수정하지 않아도 되기 때문에, 리팩토링하기 쉽고 버전 제어에서 비교가 더 좋습니다.

좋음:

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

나쁨:

enum Tiles {
    TILE_BRICK,
    TILE_FLOOR,
    TILE_SPIKE,
    TILE_TELEPORT
}

쉼표 매달기는 한 줄 목록에는 불필요합니다. 따라서 이 경우는 사용하지 마세요.

좋음:

enum Tiles {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

나쁨:

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

공백 줄(Blank lines)

함수와 클래스 정의를 두 개의 공백 줄로 묶습니다:

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)

논리 섹션을 분리하기 위해 함수 안에 하나의 공백 줄을 사용합니다.

줄 길이

코드 한 줄은 100 문자 이내로 유지합니다.

가능하다면 80 문자 이내로 유지해보세요. 이렇게 하면 작은 화면에서도 코드를 읽기 쉽고, 외부 텍스트 편집기에서 양쪽에 두 스크립트가 열려있는 화면에서도 읽기 쉽습니다. 예를 들어 서로 다른 코드 개정판을 볼 때가 있죠.

한 줄에 하나의 명령문

한 줄에 여러 명령문을 묶지 마세요. C 프로그래머라면 한 줄에 조건문을 함께 사용하지 마세요.

좋음:

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

if flag:
    print("flagged")

나쁨:

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

if flag: print("flagged")

유일한 예외라면 삼항(Ternary) 연산자일 것입니다:

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

불필요한 괄호 피하기

표현식과 조건문에 괄호를 사용하지 마십시오. 연산 명령에 필요한 경우가 아니라면, 가독성만 떨어뜨릴 것입니다.

좋음:

if is_colliding():
    queue_free()

나쁨:

if (is_colliding()):
    queue_free()

불리언(Boolean) 연산자

불리언 연산자의 영어 버전이 가장 접근하기 쉽습니다:

  • && 대신 and를 사용하세요.
  • || 대신 or를 사용하세요.

모호한 표현을 막고자 불리언 연산자 주변에 괄호를 사용합니다. 이렇게 하면 긴 표현식도 읽기 쉬워집니다.

좋음:

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

나쁨:

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

주석(Comment) 간격

표준 주석은 한 칸 띄고 시작해야 합니다. 하지만 코드에 놓은 주석은 띄지 않습니다. 이렇게 하면 코드를 비활성화한 것과 텍스트 주석을 구분하기 좋습니다.

좋음:

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

나쁨:

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

주석

In the script editor, to toggle the selected code commented, press Ctrl + K. This feature adds a single # sign at the start of the selected lines.

공백

연산자와 쉼표 뒤에는 한 칸을 띄어주세요. 그리고 딕셔너리 참조와 함수 호출에는 띄어쓰기를 피하세요.

좋음:

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

나쁨:

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

표현식이 수직으로 나열하는 띄어쓰기를 하지 마세요:

x        = 100
y        = 100
velocity = 500

따옴표

작은 따옴표로 주어진 문자열을 더 작은 문자 수로 이스케이프하게 만드는 것이 아니라면 큰 따옴표를 사용하세요. 아래의 예제를 참고하세요:

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

명명 규칙

이러한 명명 규칙은 Godot 엔진 스타일을 따릅니다. 이 규칙을 깨면 코드는 내장 명명 규칙으로 망가지며, 일관되지 않은 코드가 됩니다.

File names

Use snake_case for file names. For named classes, convert the PascalCase class name to 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

This is consistent with how C++ files are named in Godot's source code. This also avoids case sensitivity issues that can crop up when exporting a project from Windows to other platforms.

클래스와 노드

클래스와 노드 이름에는 파스칼 표기법(PascalCase)을 사용하세요:

extends KinematicBody

그리고 상수 또는 변수로 클래스를 불러올 때도 파스칼 표기법(PascalCase)을 사용하세요:

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

함수와 변수

함수와 변수 이름에는 스네이크 표기법(snake_case)를 사용하세요:

var particle_effect
func load_level():

개인 함수, 개인 변수, 사용자가 다시 정의하는 가상 메서드 함수 앞에는 밑줄 (_) 하나가 있어야 합니다:

var _counter = 0
func _recalculate_path():

시그널

시그널의 이름에는 과거형을 사용합니다:

signal door_opened
signal score_changed

상수(Constant)와 열거형(enum)

CONSTANT_CASE로 상수를 작성합니다. 다시 말해, 모든 단어는 대문자로 하고 띄어쓰기 대신 밑줄 (_)을 사용합니다:

const MAX_SPEED = 200

열거형 이름에는 파스칼 표기법(PascalCase)을 사용하고, 열거형의 멤버에는 상수와 마찬가지로 CONSTANT_CASE를 사용합니다:

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

코드 순서

이 섹션은 코드 순서에 중점을 둡니다. 서식에 관해서는 서식(Formatting)을 참고해주세요. 명명 규칙에 관해서는 명명 규칙을 참고해주세요.

제안하는 GDScript 코드 구조는 다음과 같습니다:

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

이 순서는 위에서 아래까지 코드를 읽기 쉽게 최적화했습니다. 개발자가 한 눈에 코드가 어떻게 작동하는 지 알도록, 그리고 변수 선언의 순서와 관련된 오류가 발생하지 않도록 말이죠.

이 코드 순서는 네 가지 규칙을 따릅니다:

  1. 속성과 시그널이 첫 번째로 옵니다. 그 뒤는 메서드가 나옵니다.
  2. 공개(Public) 코드가 개인(Private) 코드보다 먼저 옵니다.
  3. 가상 콜백(Virtual Callback)이 클래스의 인터페이스(Class's Interface)보다 먼저 옵니다.
  4. 객체의 구조와 초기화 함수인 _init_ready는 런타임에서 객체를 수정하도록 함수 이전에 옵니다.

클래스 선언(Declaration)

코드가 편집기에서 작동하게 하려면, 스크립트의 첫 번째 줄에 tool 키워드를 배치하세요.

class_name이 있다면 바로 아래에 배치하세요. tool 기능을 사용하면 GDScript를 프로젝트의 전역 유형으로 바꿀 수 있습니다. 자세한 설명은 GDScript 기초를 참고하세요.

다음으로, 클래스가 내장 유형을 확장하고 있다면 extends 키워드를 추가하세요.

이 밑에는 클래스에 관한 별개의 문서(Docstring)를 주석으로 넣어야 합니다. 클래스의 역할을 설명할 때 사용할 수 있습니다. 다른 동료나 사용설명서 등으로 쓸 수 있죠.

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

시그널(Signal)과 속성(Property)

Docstring이 끝나면 시그널 선언을 작성한 뒤에 속성, 즉, 멤버 변수를 작성합니다.

열거형(Enum)은 시그널 뒤에 나와야 합니다. 다른 속성의 내보내기 힌트로 사용할 수 있기 때문이죠.

그런 다음, 상수(Constant), 내보낸 변수, 공개(Public), 개인(Private), onready 변수를 순서대로 작성하세요.

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

주석

GDScript 컴파일러는 onready 변수를 _ready 콜백 바로 이전에 실행합니다. 따라서 노드 종속성을 처리할 때 사용할 수 있습니다. 말 그대로 씬에서 클래스에 의존하는 자식 노드를 가져올 수 있다는 것이죠. 위 예제에서 이 작업을 보여줍니다.

Member variables

Don't declare member variables if they are only used locally in a method, as it makes the code more difficult to follow. Instead, declare them as local variables in the method's body.

Local variables

Declare local variables as close as possible to their first use. This makes it easier to follow the code, without having to scroll too much to find where the variable was declared.

메서드(Method)와 정적(Static) 함수

클래스 속성 뒤에는 메서드가 옵니다.

엔진이 메모리에 객체를 만들 때 호출하는 _init() 콜백 메서드로 시작하세요. 그 뒤로 Godot가 씬 트리에 노드를 추가할 때 호출하는 _ready() 콜백이 옵니다.

이 함수가 객체를 어떻게 초기화할 지 보여주기 때문에 가장 먼저 와야 합니다.

_unhandled_input()_physics_process와 같은 다른 내장 가상 콜백은 다음에 나와야 합니다. 이 메서드는 객체의 메인 루프와 게임 엔진과의 상호작용을 제어합니다.

이 다음은 순서에 따라 클래스 인터페이스의 나머지인, 공개 메서드와 개인 메서드가 옵니다.

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

정적 타이핑(Static Typing)

Godot 3.1부터, GDScript는 선택 정적 타이핑을 지원합니다.

유형 힌트(Type Hint)

변수의 이름 바로 뒤에, 띄어쓰기 없이 쌍점을 배치하세요. 그러면 GDScript 컴파일러는 변수의 유형을 추론합니다.

좋음:

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

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

나쁨:

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

컴파일러가 유형 힌트를 추론할 때는 쌍점과 등호를 함께 작성합니다: :=.

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

함수를 정의할 때, 반환 유형 화살표의 양쪽에는 띄어쓰기를 추가하세요.

func heal(amount: int) -> void: