Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

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 ([method Node._physics_process],
## [method Node._unhandled_input]) to the state.

signal state_changed(previous, new)

@export var initial_state: Node
var is_active = true:
    set = set_is_active

@onready var _state = initial_state:
    set = set_state
@onready var _state_name = _state.name


func _init():
    add_to_group("state_machine")


func _enter_tree():
    print("this happens before the ready method!")


func _ready():
    state_changed.connect(_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.player_state_changed.emit(_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")
    state_changed.emit()


class State:
    var foo = 0

    func _init():
        print("Hello!")

서식

인코딩과 특수 문자

  • 줄 바꿈을 위해 라인 피드 (LF) 문자를 사용합니다. CRLF나 CR은 사용하지 않습니다 (편집기 기본 설정)

  • 각 파일의 끝에 하나의 라인 피드 문자를 사용합니다. (편집기 기본 설정)

  • 바이트 순서 표식 없이 UTF-8 인코딩을 사용합니다. (편집기 디폴트)

  • 들여쓰기로 스페이스바 대신 Tab(탭) 키를 사용합니다. (편집기 기본 설정)

들여쓰기

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

좋음:

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_dict = {
    "Name": "Bob",
    "Age": 27,
    "Job": "Mechanic",
}

enum Tile {
    BRICK,
    FLOOR,
    SPIKE,
    TELEPORT,
}

나쁨:

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

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

enum Tile {
        BRICK,
        FLOOR,
        SPIKE,
        TELEPORT,
}

쉼표 매달기(Trailing comma)

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

좋음:

var array = [
    1,
    2,
    3,
]

나쁨:

var array = [
    1,
    2,
    3
]

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

좋음:

var array = [1, 2, 3]

나쁨:

var array = [1, 2, 3,]

공백 줄(Blank lines)

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

func heal(amount):
    health += amount
    health = min(health, max_health)
    health_changed.emit(health)


func take_damage(amount, effect=null):
    health -= amount
    health = max(0, health)
    health_changed.emit(health)

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

참고

이 문서의 클래스 참조와 짧은 코드 조각에서는 클래스와 함수 정의 사이에 한 줄을 사용합니다.

줄 길이

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

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

한 줄에 하나의 명령문

가독성을 위해 GDScript 스타일 지침을 준수하려면 조건문을 포함하여 여러 문을 한 줄에 결합하지 마십시오.

좋음:

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 = "idle" if is_on_floor() else "fall"

가독성을 위해서 여러 줄로 명령문 형식 지정

특히 긴 if 문이나 중첩된 삼항 표현식이 있는 경우 여러 줄로 감싸면 가독성이 향상됩니다. 연속되는 줄은 여전히 동일한 표현식의 일부이므로 하나 대신 2개의 들여쓰기 수준을 사용해야 합니다.

GDScript에서는 괄호나 백슬래시를 사용해 여러 줄을 사용하여 명령문을 줄 바꿈할 수 있습니다. 괄호는 더 쉽게 리팩토링할 수 있도록 하기 때문에 이 스타일 가이드에서 선호됩니다. 백슬래시를 사용하면 마지막 줄 끝에 백슬래시가 포함되지 않도록 해야 합니다. 괄호를 사용하면 마지막 줄 끝에 백슬래시가 있는 것에 대해 걱정할 필요가 없습니다.

조건식을 여러 줄로 묶을 때 and/or 키워드는 이전 줄의 끝이 아니라 줄 연속의 시작 부분에 배치해야 합니다.

좋음:

var angle_degrees = 135
var quadrant = (
        "northeast" if angle_degrees <= 90
        else "southeast" if angle_degrees <= 180
        else "southwest" if angle_degrees <= 270
        else "northwest"
)

var position = Vector2(250, 350)
if (
        position.x > 200 and position.x < 400
        and position.y > 300 and position.y < 400
):
    pass

나쁨:

var angle_degrees = 135
var quadrant = "northeast" if angle_degrees <= 90 else "southeast" if angle_degrees <= 180 else "southwest" if angle_degrees <= 270 else "northwest"

var position = Vector2(250, 350)
if position.x > 200 and position.x < 400 and position.y > 300 and position.y < 400:
    pass

불필요한 괄호 피하기

표현식과 조건문에서 괄호를 사용하지 마세요. 작업 순서나 여러 줄을 묶는 데에 필요한 경우가 아니면 가독성만 떨어뜨립니다.

좋음:

if is_colliding():
    queue_free()

나쁨:

if (is_colliding()):
    queue_free()

불리언 연산자

가장 접근하기 쉬운 불리언 연산자의 영어 텍스트 버전을 선호합니다:

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

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

  • ! 대신 not을 사용하세요.

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

좋음:

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

나쁨:

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

주석 간격

일반 주석(#) 및 문서 주석(##)은 공백으로 시작해야 하지만 주석 처리하는 코드는 그렇지 않습니다. 또한 코드 영역 주석(#region/#endregion)은 정확한 구문을 따라야 하므로 공백으로 시작하면 안 됩니다.

일반 주석은 공백으로 시작해야 하지만 주석을 달은 코드는 공백으로 시작하지 않아야 합니다. 이렇게 하면 텍스트 주석을 비활성화된 코드와 구분하는 데 도움이 됩니다.

좋음:

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

나쁨:

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

참고

스크립트 편집기에서 주석 처리된 선택한 코드를 토글하려면 Ctrl + K를 누르세요. 이 기능은 선택한 줄의 시작 부분에 하나의 # 기호를 추가합니다.

인라인 주석(코드와 같은 줄에 작성된 주석)보다는 자체 라인에 주석을 작성하는 것을 선호합니다. 인라인 주석은 짧은 주석(일반적으로 최대 몇 단어)에 가장 적합합니다.

좋음:

# This is a long comment that would make the line below too long if written inline.
print("Example") # Short comment.

나쁨:

print("Example") # This is a long comment that would make this line too long if written inline.

공백

항상 연산자 주위와 쉼표 뒤에 공백 하나를 사용하십시오. 또한 사전 참조 및 함수 호출에 추가 공백을 피하십시오. 이에 대한 한 가지 예외는 여는 중괄호 뒤와 닫는 중괄호 앞에 공백을 추가해야 하는 단일 행 사전 선언의 경우입니다. 대부분의 글꼴에서 [] 문자가 ``{}``에 가깝게 보이기 때문에 사전을 배열과 시각적으로 쉽게 구별할 수 있습니다.

좋음:

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

나쁨:

position.x=5
position.y = mpos.y+10
dict ["key"] = 5
myarray = [4,5,6]
my_dictionary = {key = "value"}
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\"")

숫자

부동 소수점 숫자에서 앞뒤에 붙는 0을 생략하지 마세요. 그렇지 않으면 가독성이 떨어지고 정수와 한눈에 구별하기 어려워집니다.

좋음:

var float_number = 0.234
var other_float_number = 13.0

나쁨:

var float_number = .234
var other_float_number = 13.

16진수의 문자에는 소문자를 사용하세요. 알파벳 높이가 낮을수록 숫자를 더 읽기 쉽게 하기 때문입니다.

좋음:

var hex_number = 0xfb8c0b

나쁨:

var hex_number = 0xFB8C0B

리터럴에서 GDScript의 밑줄을 활용해 큰 숫자를 더 읽기 쉽게 만드세요.

좋음:

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

나쁨:

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

명명 규칙

이러한 명명 규칙은 Godot 엔진 스타일을 따릅니다. 이 규칙을 깨면 코드가 기본 제공 명명 규칙과 충돌해서 일관되지 않게 됩니다.

유형

연결(Connections)

예제

파일 이름

사용 사례

yaml_parser.gd

클래스

파스칼케이스

class_name YAMLParser

파일 이름

파스칼케이스

-e, --editor

함수

사용 사례

func load_level():

변수

사용 사례

var particle_effect

시그널

사용 사례

Signal 키워드

상수

CONSTANT_CASE

const MAX_SPEED = 200

열거형 이름

파스칼케이스

Android (시험용)

열거형 멤버

CONSTANT_CASE

{EARTH, WATER, AIR, FIRE}

파일 이름

파일 이름으로 스네이크_표기법(snake_case)을 사용하세요. 명명된 클래스의 경우 파스칼 표기(PascalCase)된 클래스 이름을 스네이크_표기(snake_case)로 변환하세요:

# This file should be saved as `weapon.gd`.
class_name Weapon
extends Node
# This file should be saved as `yaml_parser.gd`.
class_name YAMLParser
extends Object

이는 Godot의 소스 코드에서 C++ 파일의 이름이 어떻게 지정되는지와 일치합니다. 이는 또한 Windows에서 다른 플랫폼으로 프로젝트를 내보낼 때 발생할 수 있는 대소문자 구분 문제를 방지합니다.

클래스와 노드

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

extends CharacterBody3D

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

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

함수와 변수

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

var particle_effect
func load_level():

사용자가 오버라이드해야 하는 가상 메서드 함수, private 함수, private 변수 앞에 밑줄(_) 하나를 추가하세요:

var _counter = 0
func _recalculate_path():

시그널

시그널의 이름에는 과거형을 사용하세요:

signal door_opened
signal score_changed

상수와 열거형

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

const MAX_SPEED = 200

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

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

각 항목을 한 줄에 하나씩 열거형으로 작성합니다. 이를 통해 각 항목 위에 문서 설명을 더 쉽게 추가할 수 있으며, 항목이 추가되거나 제거될 때 버전 제어에서 더 깔끔한 차이점을 제공합니다.

좋음:

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

나쁨:

enum Element { EARTH, WATER, AIR, FIRE }

코드 순서

이 섹션에서는 코드 순서에 중점을 둡니다. 서식은 서식을 참고하세요. 명명 규칙의 경우 명명 규칙를 참고하세요.

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

01. @tool, @icon, @static_unload
02. class_name
03. extends
04. ## doc comment

05. signals
06. enums
07. constants
08. static variables
09. @export variables
10. remaining regular variables
11. @onready variables

12. _static_init()
13. remaining static methods
14. overridden built-in virtual methods:
    1. _init()
    2. _enter_tree()
    3. _ready()
    4. _process()
    5. _physics_process()
    6. remaining virtual methods
15. overridden custom methods
16. remaining methods
17. inner classes

그리고 액세스 수정자에 따라 클래스 메서드와 변수를 다음 순서로 배치합니다.

1. public
2. private

코드를 위에서 아래로 쉽게 읽을 수 있도록 순서를 최적화했으며, 코드를 처음 읽는 개발자가 어떻게 작동하는지 이해하고 변수 선언 순서와 관련된 오류를 방지할 수 있습니다.

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

  1. 속성과 시그널이 첫 번째로 옵니다. 그 뒤는 메서드가 나옵니다.

  2. 공개가 개인보다 먼저 옵니다.

  3. 가상 콜백이 클래스의 인터페이스보다 먼저 옵니다.

  4. 오브젝트의 생성 및 초기화 함수인 _init_ready는 런타임에서 오브젝트를 수정하는 함수보다 먼저 옵니다.

클래스 선언(Declaration)

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

class_name이 있다면 바로 아래에 배치하세요. tool 기능을 사용하면 GDScript를 프로젝트의 전역 타입으로 바꿀 수 있습니다. 자세한 정보는 스크립트를 클래스로 등록하기을 참조하세요.

다음으로, 클래스가 내장 타입을 확장(extend)하고 있다면 extends 키워드를 추가하세요.

그 다음에는 클래스에 대한 별개의 문서(docstring)를 주석으로 넣어야 합니다. 이를 사용하면 예를 들어, 팀원에게 클래스의 역할과 작동 방식을 설명하고, 다른 개발자가 클래스를 어떻게 사용해야 하는지를 설명할 수 있습니다.

@abstract
class_name MyNode
extends Node
## A brief description of the class's role and functionality.
##
## The description of the script, what it can do,
## and any further detail.

내부 클래스의 경우 한 줄 선언을 사용합니다.

## A brief description of the class's role and functionality.
##
## The description of the script, what it can do,
## and any further detail.
@abstract class MyNode extends Node:
    pass

시그널과 속성

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

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

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

signal player_spawned(position)

enum Job {
    KNIGHT,
    WIZARD,
    ROGUE,
    HEALER,
    SHAMAN,
}

const MAX_LIVES = 3

@export var job: Job = Job.KNIGHT
@export var max_health = 50
@export var attack = 5

var health = max_health:
    set(new_health):
        health = new_health

var _speed = 300.0

@onready var sword = get_node("Sword")
@onready var gun = get_node("Gun")

참고

GDScript 컴파일러는 onready 변수를 _ready 콜백 바로 이전에 실행합니다. 이를 사용해 노드 종속성을 캐싱할 수 있습니다. 즉, 클래스가 의존하는 씬에서 자식 노드를 가져올 수 있습니다. 위의 예제에서 이를 보여줍니다.

멤버 변수

메서드에서 지역적으로만 사용되는 경우 멤버 변수를 선언하지 마세요. 코드를 따라가기 더 어렵게 만들기 때문입니다. 대신 메서드 본문에서 지역 변수로 선언하세요.

지역 변수(Local Variables)

지역 변수를 처음 사용할 때와 최대한 가깝게 선언하세요. 이렇게 하면 변수가 선언된 위치를 찾기 위해 너무 많이 스크롤하지 않고도 코드를 더 쉽게 따라갈 수 있습니다.

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

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

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

이러 함수들은 오브젝트가 초기화되는 방법을 보여주기 때문에 먼저 와야 합니다.

_unhandled_input(), _physics_process와 같은 다른 내장된 가상 콜백은 다음에 와야 합니다. 이 메서드들은 오브젝트의 메인 루프와 게임 엔진과의 상호작용을 제어합니다.

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

func _init():
    add_to_group("state_machine")


func _ready():
    state_changed.connect(_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.player_state_changed.emit(_state.name)


func _on_state_changed(previous, new):
    print("state changed")
    state_changed.emit()

정적 타이핑

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

선언된 타입

변수의 타입을 선언하려면 <variable>: <type>을 사용하세요:

var health: int = 0

함수의 반환 타입을 선언하려면 -> <type>을 사용하세요:

func heal(amount: int) -> void:

추론된 타입

대부분의 경우 컴파일러가 ``:=``를 사용하여 유형을 유추하도록 할 수 있습니다. 유형이 할당과 동일한 줄에 작성되면 ``:=``를 선호하고, 그렇지 않으면 유형을 명시적으로 작성하는 것을 선호합니다.

좋음:

# The type can be int or float, and thus should be stated explicitly.
var health: int = 0

# The type is clearly inferred as Vector3.
var direction := Vector3(1, 2, 3)

유형이 모호할 경우 유형 힌트를 포함하고, 중복되면 유형 힌트를 생략합니다.

나쁨:

# Typed as int, but it could be that float was intended.
var health := 0

# The type hint has redundant information.
var direction: Vector3 = Vector3(1, 2, 3)

# What type is this? It's not immediately clear to the reader, so it's bad.
var value := complex_function()

그러나 컨텍스트가 누락된 경우 컴파일러는 함수의 반환 타입으로 폴백(fall back)합니다. 예를 들어 get_node()는 노드의 씬이나 파일이 메모리에 로드되지 않는 한 타입을 유추할 수 없습니다. 이 경우 타입을 명시적으로 설정해야 합니다.

좋음:

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

나쁨:

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

또는 as 키워드를 사용해 반환 타입을 캐스팅할 수 있으며, 해당 타입은 var의 타입을 추론하는 데 사용됩니다.

@onready var health_bar := get_node("UI/LifeBar") as ProgressBar
# health_bar will be typed as ProgressBar

참고

이 옵션은 유형 힌트보다 :ref:`type-safe<doc_gdscript_static_typing_safe_lines>`로 간주되지만 런타임에 유형이 일치하지 않는 경우 오류/경고 없이 변수를 ``null``로 자동 캐스팅하므로 null 안전성이 낮습니다.