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

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

나쁨:

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

var character_dict = {
        "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)

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

참고

We use a single line between classes and function definitions in the class reference and in short code snippets in this documentation.

줄 길이

코드 한 줄은 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 문이나 중첩된 삼항 표현식이 있는 경우 여러 줄로 감싸면 가독성이 향상됩니다. 연속되는 줄은 여전히 동일한 표현식의 일부이므로 하나 대신 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()

불리언(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")

참고

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

공백

항상 연산자 주위와 쉼표 뒤에는 한 칸을 띄어주세요. 또한 딕셔너리 참조 및 함수 호출에 공백을 추가하지 마세요.

좋음:

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

숫자

부동 소수점 숫자에서 앞뒤에 붙는 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 엔진 스타일을 따릅니다. 이 규칙을 깨면 코드가 기본 제공 명명 규칙과 충돌해서 일관되지 않게 됩니다.

파일 이름

파일 이름으로 스네이크_표기법(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 KinematicBody

그리고 상수 또는 변수로 클래스를 불러올 때도 파스칼 표기법(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)와 열거형(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 기초를 참고하세요.

다음으로, 클래스가 내장 타입을 확장(extend)하고 있다면 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 변수를 순서대로 작성하세요.

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

참고

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

멤버 변수

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

지역 변수(Local Variables)

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

메서드(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는 선택적 정적 타이핑을 지원합니다.

선언된 타입

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

var health: int = 0

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

func heal(amount: int) -> void:

추론된 타입

대부분의 경우 :=를 사용해 컴파일러가 타입을 추론하도록 할 수 있습니다:

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

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

좋음:

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

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

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

이 방법은 첫 번째 방법보다 더 타입에 안전한것으로 간주됩니다.

나쁨:

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