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

주석

스크립트 편집기에서 선택한 코드를 주석으로 바꾸려면 <kbd>Ctrl</kbd> <kbd>K</kbd>를 누르세요. 누르면 선택한 줄의 처음 부분에 # 기호가 생깁니다.

공백

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

좋음:

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 엔진 스타일을 따릅니다. 이 규칙을 깨면 코드는 내장 명명 규칙으로 망가지며, 일관되지 않은 코드가 됩니다.

클래스와 노드

클래스와 노드 이름에는 파스칼 표기법(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 콜백 바로 이전에 실행합니다. 따라서 노드 종속성을 처리할 때 사용할 수 있습니다. 말 그대로 씬에서 클래스에 의존하는 자식 노드를 가져올 수 있다는 것이죠. 위 예제에서 이 작업을 보여줍니다.

메서드(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: