GDScript 기초

소개

GDScript는 하이 레벨의, 동적 타입 프로그래밍 언어입니다. 문법은 Python과 유사합니다 (블록이 들여쓰기 기반이고, 많은 키워드가 비슷합니다). 이 언어의 목적은 내용 만들기 및 통합에 좋은 유연성을 바탕으로, Godot 엔진과 강하게 통합되어 최적화하는 것입니다.

역사

주석

Documentation about GDScript's history has been moved to the Frequently Asked Questions.

GDScript 예제

몇 몇 사람은 문법을 보는 것으로 언어를 더 잘 이해합니다. 따라서 GDScript를 어떻게 사용하는 지 간단한 예제를 보여 드리겠습니다.

# A file is a class!

# Inheritance

extends BaseClass

# (optional) class definition with a custom icon

class_name MyClass, "res://path/to/optional/icon.svg"


# Member variables

var a = 5
var s = "Hello"
var arr = [1, 2, 3]
var dict = {"key": "value", 2: 3}
var typed_var: int
var inferred_type := "String"

# Constants

const ANSWER = 42
const THE_NAME = "Charly"

# Enums

enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY}
enum Named {THING_1, THING_2, ANOTHER_THING = -1}

# Built-in vector types

var v2 = Vector2(1, 2)
var v3 = Vector3(1, 2, 3)


# Function

func some_function(param1, param2):
    var local_var = 5

    if param1 < local_var:
        print(param1)
    elif param2 > 5:
        print(param2)
    else:
        print("Fail!")

    for i in range(20):
        print(i)

    while param2 != 0:
        param2 -= 1

    var local_var2 = param1 + 3
    return local_var2


# Functions override functions with the same name on the base/parent class.
# If you still want to call them, use '.' (like 'super' in other languages).

func something(p1, p2):
    .something(p1, p2)


# Inner class

class Something:
    var a = 10


# Constructor

func _init():
    print("Constructed!")
    var lv = Something.new()
    print(lv.a)

이전에 C, C++, C#과 같은 정적 타입 언어에 대한 경험이 있지만, 한 번도 동적 타입 언어를 써 본 적이 없다면, 이 튜토리얼을 읽는 것이 좋습니다: GDScript: 동적 언어 소개.

언어

여기서부터 GDScript의 개요입니다. 어떤 메서드가 배열 혹은 다른 객체에 이용할 수 있는지와 같은 세부 사항은 클래스 설명의 링크를 확인해주세요.

식별자(Identifier)

알파벳 문자로 제한되는 문자열 (a부터 z, A부터 Z), 숫자 (0부터 9), _는 식별자입니다. 추가로 식별자는 숫자로 시작할 수 없습니다. 식별자는 대소문자를 구분합니다 (fooFOO와 다릅니다).

키워드(Keyword)

다음은 언어에서 지원하는 키워드 목록입니다. 키워드는 예약된 단어(토큰)이기 때문에, 식별자로 사용할 수 없습니다. 다음 섹션에 나열된 (in, not, and 혹은 or 와 같은) 연산자와 내장 타입 이름 역시 예약된 단어입니다.

키워드는 GDScript tokenizer에서 정의됩니다. 정체가 궁금하다면 확인해보세요.

키워드 설명
if if/else/elif를 참고하세요.
elif if/else/elif를 참고하세요.
else if/else/elif를 참고하세요.
for for를 참고하세요.
while while을 참고하세요.
match match를 참고하세요.
break 현재의 for 또는 while 루프 실행을 끝냅니다.
continue 즉시 for 또는 while 루프의 다음 반복으로 건너 뜁니다.
pass 명령문이 문법적으로는 필요하지만 실행할 만한 코드가 마땅치 않을 때 사용됩니다. 예: 빈 함수.
return 함수에서 값을 반환합니다.
class 클래스를 정의합니다.
extends 현재 클래스로 확장할 클래스를 정의합니다.
is 변수가 주어진 클래스를 확장하는지, 혹은 변수가 주어진 내장 유형인지 여부를 테스트합니다.
as 가능하다면 주어진 유형으로 값을 캐스트합니다.
self 현재 클래스 인스턴스를 참조합니다.
tool 편집기에서 스크립트를 실행합니다.
signal 시그널을 정의합니다.
func 함수를 정의합니다.
static 정적 함수를 정의합니다. 정적 멤버 변수를 허용하지 않습니다.
const 상수를 정의합니다.
enum 열거형을 정의합니다.
var 변수를 정의합니다.
onready 스크립트가 붙은 노드이고 노드의 자식이 씬 트리의 일부분인 경우, 변수를 초기화합니다.
export 변수를 리소스와 함께 저장하고 편집기에서 보고 수정할 수 있게 만듭니다.
setget 변수에 대한 setter(세터)와 getter(게터) 함수를 정의합니다.
breakpoint 디버거 중단점 용 편집기 도우미.
preload 클래스나 변수를 미리 불러옵니다(Preload). 리소스로 취급되는 클래스를 참고하세요.
yield Coroutine(코루틴)을 지원합니다. yield를 갖는 Coroutine(코루틴)을 참고하세요.
assert 조건을 가정(Assert)하고 실패 시 오류를 기록합니다. 디버그가 아닌 빌드에서는 무시됩니다. Assert 키워드를 참고하세요.
remote 네트워킹 RPC(Remote Procedure Call, 원격 프로시저 호출) 주석. 하이 레벨 멀티플레이어 문서를 참고하세요.
master 네트워킹 RPC(Remote Procedure Call, 원격 프로시저 호출) 주석. 하이 레벨 멀티플레이어 문서를 참고하세요.
puppet 네트워킹 RPC(Remote Procedure Call, 원격 프로시저 호출) 주석. 하이 레벨 멀티플레이어 문서를 참고하세요.
remotesync 네트워킹 RPC(Remote Procedure Call, 원격 프로시저 호출) 주석. 하이 레벨 멀티플레이어 문서를 참고하세요.
mastersync 네트워킹 RPC(Remote Procedure Call, 원격 프로시저 호출) 주석. 하이 레벨 멀티플레이어 문서를 참고하세요.
puppetsync 네트워킹 RPC(Remote Procedure Call, 원격 프로시저 호출) 주석. 하이 레벨 멀티플레이어 문서를 참고하세요.
PI PI 상수.
TAU TAU 상수.
INF 무한대 상수. 비교로 사용됩니다.
NAN NAN (Not A Number, 숫자 아님) 상수. 비교로 사용됩니다.

연산자(Operator)

다음은 지원하는 연산자 목록과 우선 순위입니다.

연산자 설명
x[index] 구독(Subscription) (가장 높은 우선 순위)
x.attribute 속성 참조
foo() 함수 호출
is 인스턴스 유형 검사기
~ 비트 단위 NOT
-x 음수 / 단항 부정
* / %

곱하기 / 나누기 / 나머지

이 연산자는 C++의 연산자와 같습니다. 정수 나누기는 소수점 부분을 잘라냅니다. 그리고 % 연산자는 정수에만 사용됩니다 (실수 용 연산자는 "fmod"입니다)

+ 배열의 추가 / 연결
- 빼기
<< >> 비트 자리 옮김
& 비트 단위 AND
^ 비트 단위 XOR
| 비트 단위 OR
< > == != >= <= 비교
in 내용 테스트
! not 불리언 NOT
and && 불리언 AND
or || 불리언 OR
if x else 삼항 if/else
as Type casting
= += -= *= /= %= &= |= 할당 (가장 낮은 우선 순위)

리터럴(Literal)

리터럴 유형
45 기본 10진법 정수
0x8F51 기본 16진법 정수
0b101010 기본 2진법 정수
3.14, 58.1e-10 부동 소수점 숫자 (실수)
"안녕하세요", "안녕" 문자열
"""안녕하세요""" 여러 줄 문자열
@"Node/Label" 노드 경로(NodePath) 혹은 문자열 이름(StringName)
$NodePath get_node("NodePath")의 짧은 표현

주석(Comment)

#부터 줄 끝까지는 주석으로 간주되어 무시됩니다.

# This is a comment.

내장 유형(Built-in type)

내장 유형은 스택에 할당됩니다. 이 유형은 값으로 전달됩니다. 즉, 사본은 각 할당에서, 혹은 유형이 인수로서 함수로 전달될 때 만들어집니다. 유일한 예외 사항은 배열(Array)딕셔너리(Dictionary)로, 참조로 전달되기 때문에 공유됩니다. (PoolByteArray와 같은 풀 형식의 배열은 여전히 값으로 전달됩니다.)

기본 내장 유형

GDScript의 변수에는 여러 내장 유형을 할당할 수 있습니다.

null

null은 빈 데이터 유형으로 정보를 갖고 있지 않습니다. 그리고 어떤 값이라도 할당할 수 없습니다.

bool

"불리언(Boolean)"의 약자로, 오직 true 또는 false만 갖습니다.

int

"정수(Integer)"의 약자로, 모든 숫자 (양수와 음수)를 저장합니다. 64비트 값으로 저장하며, 이는 C++에서 "int64_t"와 같습니다.

float

소수점을 사용한 소수를 포함하는 실수를 저장합니다. 64비트 값으로 저장되며, 이는 C++에서 "double"과 같습니다. 참고: 현재로서는 Vector2, Vector3, PoolRealArray와 같은 데이터 구조는 32비트 단정도(Single-precision) "float" 값을 저장합니다.

String

A sequence of characters in Unicode format. Strings can contain the following escape sequences:

Escape sequence Expands to
\n Newline (line feed)
\t Horizontal tab character
\r Carriage return
\a Alert (beep/bell)
\b Backspace
\f Formfeed page break
\v Vertical tab character
\" Double quote
\' Single quote
\\ Backslash
\uXXXX Unicode codepoint XXXX (hexadecimal, case-insensitive)

GDScript also supports GDScript 형식 문자열(format strings).

벡터 내장 유형

Vector2

2D 벡터 유형으로, xy 영역을 갖고 있습니다. 배열로 접근할 수도 있습니다.

Rect2

2D 사각형 유형으로 두 개의 벡터 영역을 갖고 있습니다: positionsize. 또한 position + size를 의미하는 end 영역을 갖습니다.

Vector3

3D 벡터 유형으로, x, y, z 영역을 갖고 있습니다. 배열로 접근할 수도 있습니다.

Transform2D

3x2 행렬(Matrix)로 2D 변형에 사용됩니다.

Plane

표준화된 형태의 3D 평면(Plane) 유형으로 normal 벡터 영역과 d 스칼라 거리를 갖고 있습니다.

Quat

사원수(Quaternion)는 3D 회전을 표현하기 위해 사용되는 데이터 유형입니다. 회전 값을 보간하는 용도로 사용됩니다.

AABB

축이 정렬된 경계 사각형으로 (혹은 3D 상자) 2개의 벡터 영역을 갖습니다: positionsize. 혹은 position + size를 의미하는 end 영역을 갖습니다.

Basis

3x3 행렬로 3D 회전과 크기 조정에 사용됩니다. 3개의 벡터 영역 (x, y, z)을 갖습니다. 3D 벡터의 배열로 접근할 수도 있습니다.

Transform

3D 변형(Transform)으로, Basis 영역 basis와 Vector3 영역 origin을 갖습니다.

엔진 내장 타입

Color

색상(Color) 데이터 유형으로 r, g, b, a 영역을 갖습니다. 색조/채도/명도 용으로 h, s, v로 접근할 수도 있습니다.

NodePath

노드로 컴파일된 경로로, 주로 씬 시스템에서 사용됩니다. 쉽게 문자열로 할당하거나 할당될 수 있습니다.

RID

리소스 ID (RID). 서버는 불투명한 데이터를 참조하기 위해 제네릭 RID를 사용합니다.

Object

내장 유형이 아닌 모든 것의 기본 클래스.

컨테이너(Container) 내장 유형

Array

임의 객체 유형의 일반적인 시퀀스(Sequence)로, 다른 배열(Array)이나 딕셔너리(Dictionary)를 포함합니다 (아래를 참고하세요). 배열의 크기는 동적으로 조절할 수 있습니다. 배열은 인덱스 0부터 번호를 매깁니다. 번호를 음수로 하면 배열의 끝부터 셉니다.

var arr = []
arr = [1, 2, 3]
var b = arr[1] # This is 2.
var c = arr[arr.size() - 1] # This is 3.
var d = arr[-1] # Same as the previous line, but shorter.
arr[0] = "Hi!" # Replacing value 1 with "Hi!".
arr.append(4) # Array is now ["Hi!", 2, 3, 4].

GDScript 배열은 속도를 위해 선형적으로 메모리에 할당됩니다. (수 만개보다 더) 많은 배열은 메모리 단편화를 발생할 수 있습니다. 이것이 염려된다면 특수 유형의 배열을 사용할 수 있습니다. 이 배열에는 하나의 데이터 유형만 들어갈 수 있습니다. 메모리 단편화를 방지할 뿐더러, 더 적은 메모리를 사용하지만 원자적이고 기본 배열보다 더 느리게 실행됩니다. 따라서 많은 데이터 집합을 사용할 때만 추천합니다:

Dictionary

고유 키로 참조되는 값을 갖는 연관 컨테이너입니다.

var d = {4: 5, "A key": "A value", 28: [1, 2, 3]}
d["Hi!"] = 0
d = {
    22: "value",
    "some_key": 2,
    "other_key": [2, 3, 4],
    "more_key": "Hello"
}

Lua-style table syntax is also supported. Lua-style uses = instead of : and doesn't use quotes to mark string keys (making for slightly less to write). However, keys written in this form can't start with a digit (like any GDScript identifier).

var d = {
    test22 = "value",
    some_key = 2,
    other_key = [2, 3, 4],
    more_key = "Hello"
}

존재하는 딕셔너리에 키를 추가하려면, 기존 키와 같은 방식으로 접근한 뒤 할당해야 합니다:

var d = {} # Create an empty Dictionary.
d.waiting = 14 # Add String "waiting" as a key and assign the value 14 to it.
d[4] = "hello" # Add integer 4 as a key and assign the String "hello" as its value.
d["Godot"] = 3.01 # Add String "Godot" as a key and assign the value 3.01 to it.

var test = 4
# Prints "hello" by indexing the dictionary with a dynamic key.
# This is not the same as `d.test`. The bracket syntax equivalent to
# `d.test` is `d["test"]`.
print(d[test])

주석

The bracket syntax can be used to access properties of any Object, not just Dictionaries. Keep in mind it will cause a script error when attempting to index a non-existing property. To avoid this, use the Object.get() and Object.set() methods instead.

데이터(Data)

변수(Variable)

변수는 클래스 멤버, 혹은 함수에 지역으로 존재할 수 잇습니다. 변수는 var 키워드 만들 수 있으며, 초기화에 값을 지정할 수 있습니다.

var a # Data type is 'null' by default.
var b = 5
var c = 3.8
var d = b + c # Variables are always initialized in order.

변수는 선택적으로 유형을 지정할 수 있습니다. 유형을 지정하면, 그 변수는 항상 같은 유형으로 제한되며, 유형에 맞지 않는 값을 할당하려 하면 오류가 발생합니다.

유형은 변수 선언에서 변수 이름 뒤에 : (쌍점) 기호를 붙인 다음, 유형 이름을 지정합니다.

var my_vector2: Vector2
var my_node: Node = Sprite.new()

변수가 선언 내에서 초기화된다면, 유형은 유추할 수 있게 됩니다. 따라서 유형 이름을 생략하는 것이 가능합니다:

var my_vector2 := Vector2() # 'my_vector2' is of type 'Vector2'.
var my_node := Sprite.new() # 'my_node' is of type 'Sprite'.

유형 추론은 할당된 값이 정의된 유형이 있는 경우에만 가능합니다. 그렇지 않으면 오류가 발생합니다.

올바른 타입은 다음과 같습니다:

  • 내장 유형 (Array, Vector2, int, String 등).
  • 엔진 클래스 (Node, Resource, Reference 등).
  • 스크립트 리소스가 포함된 상수 이름 (const MyScript = preload("res://my_script.gd")를 선언했다면 MyScript가 이에 속합니다).
  • 같은 스크립트에서 유효 범위를 준수하는 다른 클래스 (같은 유효 범위에서 class InnerClass 안에 class NestedClass를 선언했다면 InnerClass.NestedClass가 이에 속합니다).
  • class_name 키워드로 선언된 스크립트 클래스.

캐스팅(Casting)

유형이 있는 변수에는 그에 맞는 유형을 지닌 값을 지정해야 합니다. 만약 값을 특정 유형으로, 특히 객체 유형으로 강제 변환해야 한다면, 캐스팅 연산자 as를 사용해야 합니다.

객체 유형 간 캐스팅에서 값이 캐스트 유형과 같은 유형이거나 하위 유형이라면 같은 객체를 만듭니다.

var my_node2D: Node2D
my_node2D = $Sprite as Node2D # Works since Sprite is a subtype of Node2D.

값이 하위 유형이 아니라면 캐스팅 연산자는 null 값을 내보냅니다.

var my_node2D: Node2D
my_node2D = $Button as Node2D # Results in 'null' since a Button is not a subtype of Node2D.

내장 유형의 경우, 가능하다면 강제로 유형을 변환하지만, 불가능하다면 오류를 발생합니다.

var my_int: int
my_int = "123" as int # The string can be converted to int.
my_int = Vector2() as int # A Vector2 can't be converted to int, this will cause an error.

캐스팅은 더 유형에 안전한 변수를 만드는데 유용합니다. 씬 트리에서 상호작용하다 보면 변수의 유형에 불확실해질 수도 있기 때문이죠:

# Will infer the variable to be of type Sprite.
var my_sprite := $Character as Sprite

# Will fail if $AnimPlayer is not an AnimationPlayer, even if it has the method 'play()'.
($AnimPlayer as AnimationPlayer).play("walk")

상수

상수는 변수와 비슷하지만, 상수 또는 상수 식이어야만 하며 초기화 시 값이 지정되어야 합니다.

const A = 5
const B = Vector2(20, 20)
const C = 10 + 20 # Constant expression.
const D = Vector2(20, 30).x # Constant expression: 20.
const E = [1, 2, 3, 4][0] # Constant expression: 1.
const F = sin(20) # 'sin()' can be used in constant expressions.
const G = x + 20 # Invalid; this is not a constant expression!
const H = A + 20 # Constant expression: 25 (`A` is a constant).

상수의 유형은 지정된 값에서 유추할 수 있지만, 명시적인 유형 지정을 추가할 수도 있습니다:

const A: int = 5
const B: Vector2 = Vector2()

맞지 않은 유형의 값을 지정하면 오류가 발생합니다.

주석

Since arrays and dictionaries are passed by reference, constants are "flat". This means that if you declare a constant array or dictionary, it can still be modified afterwards. They can't be reassigned with another value though.

열거형(Enum)

열거형은 기본적으로 상수의 축약형으로, 연속적인 정수를 일부 상수에 지정할 때 꽤 유용합니다.

열거형에 이름을 전달하면, 열거형은 해당 이름의 상수 딕셔너리 안에 모든 키를 넣습니다.

중요

Godot 3.1부터는, 이름있는 열거형에서 키(Key)는 전역 상수로 등록할 수 없습니다. 키에 접근하려면 열거형의 이름이 앞에 있어야합니다 (이름.KEY). 아래의 예제를 참고하세요.

enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}
# Is the same as:
const TILE_BRICK = 0
const TILE_FLOOR = 1
const TILE_SPIKE = 2
const TILE_TELEPORT = 3

enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT}
# Is the same as:
const State = {STATE_IDLE = 0, STATE_JUMP = 5, STATE_SHOOT = 6}
# Access values with State.STATE_IDLE, etc.

함수(Function)

함수는 항상 클래스에 속합니다. 변수 조회 범위의 우선 순위는 다음과 같습니다: 지역(Local) → 클래스 멤버(Class Member) → 전역(Global). self 변수는 항상 사용할 수 있고, 클래스 멤버에 접근할 수 있는 설정으로 제공합니다. 하지만 항상 필요한 것은 아닙니다 (함수의 첫 번째 인수로 보내서는 됩니다. Python과는 다른 점이죠).

func my_function(a, b):
    print(a)
    print(b)
    return a + b  # Return is optional; without it 'null' is returned.

함수는 언제든지 반환(return)할 수 있습니다. 기본 반환 값은 null입니다.

그리고 함수는 인수와 반환 값의 유형 지정을 할 수 있습니다. 인수의 유형은 변수에서와 비슷한 방식으로 추가할 수 있습니다:

func my_function(a: int, b: String):
    pass

함수 인수가 기본 값을 갖는다면, 유형을 추론할 수 있습니다:

func my_function(int_arg := 42, String_arg := "string"):
    pass

함수의 반환 유형은 화살표 토큰 (->)을 사용하여 인수 목록 뒤에 지정할 수 있습니다:

func my_int_function() -> int:
    return 0

반환 유형을 갖는 함수는 반드시 그에 맞는 값을 반환해야 합니다. 유형을 void로 설정하면 함수는 아무 것도 반환하면 안 됩니다. Void 함수는 return 키워드로 함수에서 일찍 반환할 수 있지만, 거기서 값을 반환하면 안 됩니다.

void_function() -> void:
    return # Can't return a value

주석

Void가 아닌 함수는 반드시 항상 값을 반환해야 합니다. 따라서 코드가 (if/else 구조와 같은) 분기문을 갖고 있다면, 모든 가능한 경로에 반환이 필요합니다. 다시 말해 if 블록 안에 return을 넣어 줬지만, 그 이후에는 하지 않았다면, 편집기는 오류를 발생합니다. 조건에 맞지 않아 블록이 실행하지 않았다면, 함수는 올바른 값을 반환할 수 없기 때문이죠.

함수 참조하기(Referencing functions)

Python과 달리, 함수는 GDScript에서 최상위 클래스 객체가 아닙니다. 즉, 함수를 변수에 저장할 수 없고, 다른 함수에 인수로 전달되거나 다른 함수로부터 반환될 수 없습니다. 성능의 문제 때문입니다.

런타임에 함수를 이름으로 참조하려면 (예: 그것을 변수로 저장하거나, 다른 함수에 인수로 전달하는 경우), call이나 funcref 도우미를 사용해야 합니다:

# Call a function by name in one step.
my_node.call("my_function", args)

# Store a function reference.
var my_func = funcref(my_node, "my_function")
# Call stored function reference.
my_func.call_func(args)

정적 함수(Static functions)

함수를 정적으로 선언할 수 있습니다. 함수가 정적인 상태라면, 인스턴스 멤버 변수나 self로 접근할 수 없습니다. 주로 도우미 함수의 라이브러리를 만드는데 유용합니다:

static func sum2(a, b):
    return a + b

명령문(Statement)과 제어 흐름(Control Flow)

명령문은 표준으로 할당(Assignment), 함수 호출(Function Call), 제어 흐름 (Control Flow) 구조 등이 될 수 있습니다. 명령문 구분 기호인 ;의 사용은 자유입니다.

if/else/elif

간단한 조건은 if/else/elif 문법을 사용해 만들 수 있습니다. 조건문 주변에 괄호를 씌워도 되지만, 필수는 아닙니다. 탭 기반 들여쓰기의 특성을 감안하여, elifelse/if 대신 사용하여 들여쓰기 높이를 유지할 수 있습니다.

if [expression]:
    statement(s)
elif [expression]:
    statement(s)
else:
    statement(s)

간단한 명령문은 조건처럼 같은 줄에 적을 수 있습니다:

if 1 + 1 == 2: return 2 + 2
else:
    var x = 3 + 3
    return x

때로는, 불리언(Boolean) 표현식을 기반으로 다른 초기 값을 지정해야 하는 경우가 있습니다. 이 경우에는 삼항 표현식을 쓰면 됩니다:

var x = [value] if [expression] else [value]
y += 3 if y < 10 else -1

while

간단한 루프는 while 문법으로 만들 수 있습니다. 루프는 break를 사용하여 끊거나 continue를 사용하여 계속 갈 수 있습니다:

while [expression]:
    statement(s)

for

To iterate through a range, such as an array or table, a for loop is used. When iterating over an array, the current array element is stored in the loop variable. When iterating over a dictionary, the key is stored in the loop variable.

for x in [5, 7, 11]:
    statement # Loop iterates 3 times with 'x' as 5, then 7 and finally 11.

var dict = {"a": 0, "b": 1, "c": 2}
for i in dict:
    print(dict[i]) # Prints 0, then 1, then 2.

for i in range(3):
    statement # Similar to [0, 1, 2] but does not allocate an array.

for i in range(1, 3):
    statement # Similar to [1, 2] but does not allocate an array.

for i in range(2, 8, 2):
    statement # Similar to [2, 4, 6] but does not allocate an array.

for c in "Hello":
    print(c) # Iterate through all characters in a String, print every letter on new line.

for i in 3:
    statement # Similar to range(3)

for i in 2.2:
    statement # Similar to range(ceil(2.2))

match

match 명령문은 프로그램 실행을 분기화하는 데 사용됩니다. 이것은 많은 다른 언어에서 찾을 수 있는 switch 명령문과 유사하지만 몇 가지 추가 기능을 제공합니다.

기본 문법:

match [expression]:
    [pattern](s):
        [block]
    [pattern](s):
        [block]
    [pattern](s):
        [block]

switch 명령문에 익숙한 사람들을 위한 집중 강좌:

  1. switchmatch로 바꾸세요.
  2. case를 제거하세요.
  3. 모든 break를 제거하세요. 기본적으로 break가 되지 않도록 하고 싶다면, 다른 언어의 fallthrough 키워드처럼 continue를 사용할 수 있습니다.
  4. default를 하나의 밑줄로 변경하세요.

제어 흐름(Control flow):

The patterns are matched from top to bottom. If a pattern matches, the first corresponding block will be executed. After that, the execution continues below the match statement. You can use continue to stop execution in the current block and check for an additional match in the patterns below it.

6 가지 패턴 유형이 있습니다:

  • 상수 패턴(Constant pattern)

    숫자와 문자열과 같은 상수 원시값(Primitive):

    match x:
        1:
            print("We are number one!")
        2:
            print("Two are better than one!")
        "test":
            print("Oh snap! It's a string!")
    
  • 변수 패턴(Variable pattern)

    변수/열거형의 내용을 맞춰봅니다:

    match typeof(x):
        TYPE_REAL:
            print("float")
        TYPE_STRING:
            print("text")
        TYPE_ARRAY:
            print("array")
    
  • 임의 문자 기호 패턴(Wildcard pattern)

    이 패턴은 모든 것을 대조합니다. 하나의 밑줄로 쓸 수 있습니다.

    다른 언어의 switch 명령문의 default와 같은 의미로 쓰입니다:

    match x:
        1:
            print("It's one!")
        2:
            print("It's one times two!")
        _:
            print("It's not 1 or 2. I don't care to be honest.")
    
  • 바인딩 패턴(Binding pattern)

    바인딩 패턴은 새 변수를 도입합니다. 임의 문자 기호 패턴과 마찬가지로, 전부를 비교합니다. 그리고 값에 이름을 부여합니다. 이는 특히 배열 패턴과 딕셔너리 패턴에 유용합니다:

    match x:
        1:
            print("It's one!")
        2:
            print("It's one times two!")
        var new_var:
            print("It's not 1 or 2, it's ", new_var)
    
  • 배열 패턴(Array pattern)

    배열을 비교합니다. 배열 패턴의 각 단일 요소도 패턴이므로, 패턴을 중첩할 수 있습니다.

    배열의 길이가 먼저 테스트 되고, 그것이 패턴과 같은 크기이어야 합니다, 그렇지 않으면 패턴이 일치하지 않습니다.

    개방형 배열(Open-ended array): 배열의 마지막 하위 패턴을 ..으로 만들어서 패턴보다 배열이 더 커질 수 있습니다.

    각 하위 패턴은 쉼표로 분리해야 합니다.

    match x:
        []:
            print("Empty array")
        [1, 3, "test", null]:
            print("Very specific array")
        [var start, _, "test"]:
            print("First element is ", start, ", and the last is \"test\"")
        [42, ..]:
            print("Open ended array")
    
  • 딕셔너리 패턴(Dictionary pattern)

    배열 패턴과 같은 방식으로 작동합니다. 모든 키는 일정한 패턴이어야 합니다.

    딕셔너리의 크기가 먼저 테스트 되고, 그것이 패턴과 같은 크기이어야 합니다, 그렇지 않으면 패턴이 일치하지 않습니다.

    **개방형 딕셔너리**(Open-ended dictionary): 딕셔너리의 마지막 하위 패턴을 ..으로 만들어서 패턴보다 딕셔너리가 더 커질 수 있습니다.

    모든 하위 패턴은 쉼표로 분리되어야 합니다.

    값을 지정하지 않으면, 키의 존재 여부만 확인됩니다.

    값 패턴과 키 패턴은 :로 분리합니다.

    match x:
        {}:
            print("Empty dict")
        {"name": "Dennis"}:
            print("The name is Dennis")
        {"name": "Dennis", "age": var age}:
            print("Dennis is ", age, " years old.")
        {"name", "age"}:
            print("Has a name and an age, but it's not Dennis :(")
        {"key": "godotisawesome", ..}:
            print("I only checked for one entry and ignored the rest")
    
  • 여러 패턴(Multiple patterns)

    여러 패턴을 쉼표로 분리해서 지정할 수 있습니다. 이 패턴들 사이에서 바인딩은 허용되지 않습니다.

    match x:
        1, 2, 3:
            print("It's 1 - 3")
        "Sword", "Splash potion", "Fist":
            print("Yep, you've taken damage")
    

클래스(Class)

기본적으로 모든 스크립트 파일은 이름 없는 클래스입니다. 이 경우에는, 상대 경로나 절대 경로와 같은 파일의 경로를 사용해서 참조만 할 수 있습니다. 예를 들어, 스크립트 파일의 이름을 characrer.gd라고 지었다면 다음과 같이 참조합니다:

# Inherit from 'Character.gd'.

extends "res://path/to/character.gd"

# Load character.gd and create a new node instance from it.

var Character = load("res://path/to/character.gd")
var character_node = Character.new()

대신 클래스에 이름을 붙여서 Godot 편집기의 새 유형으로 등록할 수 있습니다. 이를 위해, class_name 키워드를 사용합니다. 개별적으로 쉼표를 붙인 다음, 아이콘으로 사용할 이미지의 경로를 입력할 수 있습니다. 만든 클래스는 새 아이콘을 가진 채로 편집기에서 볼 수 있습니다:

# Item.gd

extends Node
class_name Item, "res://interface/icons/item.png"
../../../_images/class_name_editor_register_example.png

클래스 파일 예제입니다:

# Saved as a file named 'character.gd'.

class_name Character


var health = 5


func print_health():
    print(health)


func print_this_script_three_times():
    print(get_script())
    print(ResourceLoader.load("res://character.gd"))
    print(Character)

주석

Godot의 클래스 문법은 간결합니다: 오직 멤버 변수 혹은 함수만 가질 수 있습니다. 정적 함수를 사용할 수 있지만, 정적 멤버 변수는 사용할 수 없습니다. 이처럼 엔진은 인스턴스를 만드는 매 시간마다 변수를 초기화합니다. 그리고 여기에는 배열과 딕셔너리가 포함됩니다. 스레드 안전의 정신이 비롯된 것입니다. 여러 스레드에서 스크립트가 사용자 모르게 초기화될 수 있기 때문입니다.

상속(Inheritance)

(파일로 저장한) 클래스는 여기서 상속될 수 있습니다:

  • 전역 클래스(Global class).
  • 다른 클래스 파일.
  • 다른 클래스 파일 안의 내부 클래스.

다중 상속은 허용되지 않습니다.

extends 키워드를 사용하여 상속합니다:

# Inherit/extend a globally available class.
extends SomeClass

# Inherit/extend a named class file.
extends "somefile.gd"

# Inherit/extend an inner class in another file.
extends "somefile.gd".SomeInnerClass

주어진 인스턴스가 주어진 클래스로부터 상속받았는지 확인하려면, is 키워드를 사용할 수 있습니다:

# Cache the enemy class.
const Enemy = preload("enemy.gd")

# [...]

# Use 'is' to check inheritance.
if entity is Enemy:
    entity.apply_damage()

부모 클래스에서 (다시 말해, 현재 클래스에서 extend된 클래스에서) 함수를 호출하려면, 함수 이름 앞에 .을 입력하세요:

.base_func(args)

특히 확장하는 클래스의 함수는 부모 클래스에서 같은 이름의 함수로 교체한다는 점에서 유용합니다. 확장하는 클래스의 함수를 계속 호출하고 싶으면, 접두사로 .을 붙이면 됩니다 (다른 언어에서 super 키워드처럼 말이죠):

func some_func(x):
    .some_func(x) # Calls the same function on the parent class.

주석

_init과 같은 기본 함수와 대부분의 알림, _enter_tree, _exit_tree, _process, _physics_process등은 모든 부모 클래스에서 자동으로 호출할 수 있습니다. 함수를 오버로드(Overload)할 때 명시척으로 호출하지 않아도 됩니다.

클래스 생성자(Class Constructor)

클래스 인스턴스화라고 부르는 클래스 생성자는 _init이라고 부릅니다. 앞에서 말했듯이, 부모 클래스의 생성자는 클래스를 상속할 때 자동으로 호출됩니다. 따라서 보통 명시적으로 _init()을 호출하지 않습니다.

위의 .some_func 함수로 보여준 표준 함수의 호출과는 달리, 상속받은 클래스의 생성자가 인수를 갖는다면, 이와 같이 전달됩니다:

func _init(args).(parent_args):
   pass

예제를 통해서 더 쉽게 설명해 보겠습니다. 다음 시나리오를 생각해보세요:

# State.gd (inherited class)
var entity = null
var message = null


func _init(e=null):
    entity = e


func enter(m):
    message = m


# Idle.gd (inheriting class)
extends "State.gd"


func _init(e=null, m=null).(e):
    # Do something with 'e'.
    message = m

유의해야 할 몇 가지 사항이 있습니다:

  1. 상속받은 클래스 (State.gd)가 인수를 갖는 _init 생성자를 정의한다면 (여기서 인수는 e``가 되겠죠), 상속하는 클래스 (``Idle.gd)는 반드시 _init을 정의해야 하고, State.gd에서 적절한 매개변수를 _init으로 전달해야 합니다.

  2. idle.gd는 부모 클래스 State.gd와는 다른 개수의 인수를 가질 수 있습니다.

  3. 위의 예제에서 State.gd`` 생성자로 전달된 eIdle.gd로 전달된 e와 같습니다.

  4. Idle.gd_init 생성자가 0 개의 인수를 갖더라도, 어떤 값을 State.gd 부모 클래스로 전달해야 합니다. 그것이 쓸모가 없더라도 말이죠. 이것으로 알게된 것은 기본 생성자에서 변수 뿐만 아니라 리터럴도 전달할 수 있다는 것입니다. 예:

    # Idle.gd
    
    func _init().(5):
        pass
    

내부 클래스(Inner Class)

클래스 파일은 내부 클래스를 가질 수 있습니다. 내부 클래스는 class 키워드로 정의할 수 있습니다. 내부 클래스는 ClassName.new() 함수로 인스턴스될 수 있습니다.

# Inside a class file.

# An inner class in this class file.
class SomeInnerClass:
    var a = 5


    func print_value_of_a():
        print(a)


# This is the constructor of the class file's main class.
func _init():
    var c = SomeInnerClass.new()
    c.print_value_of_a()

리소스로 취급되는 클래스

파일로 저장된 클래스는 리소스로 취급됩니다. 다른 클래스에서 접근하려면 반드시 디스크에서 불러와야 합니다. 불러오려면 loadpreload 함수를 사용할 수 있습니다 (아래를 참고하세요). 불러온 클래스 리소스를 인스턴스하려면 클래스 객체에 new 함수를 호출해야 합니다:

# Load the class resource when calling load().
var my_class = load("myclass.gd")

# Preload the class only once at compile time.
const MyClass = preload("myclass.gd")


func _init():
    var a = MyClass.new()
    a.some_function()

내보내기(Export)

주석

내보내기에 관한 서술은 GDScript 내보내기(Export)로 옮겨졌습니다.

Setter(세터)/Getter(게터)

클래스 멤버 변수가 어떤 이유로든 언제 변경되는지를 아는 것은 종종 유용합니다. 어떤 방식으로 그것의 접근을 캡슐화 하는 것이 필요할 수도 있습니다.

이를 위해, GDScript는 setget 키워드를 사용하는 setter/getter 문법을 제공합니다. 변수 정의 바로 뒤에 사용됩니다:

var variable = value setget setterfunc, getterfunc

변수의 값이 (클래스에서 지역 범위의 사용이 아닌) 외부 소스에 의해 수정되었다면, (위 예시에서 setterfunc에 해당하는) Setter 함수가 호출됩니다. 호출은 값이 변하기 에 일어납니다. Setter는 새 값으로 수행할 작업을 결정해야 합니다. 반대로, 변수에 접근할 때, (위 예시에서 getterfunc에 해당하는) Getter 함수는 반드시 그에 맞는 값을 반환합니다. 아래는 예제입니다:

var my_var setget my_var_set, my_var_get


func my_var_set(new_value):
    my_var = new_value


func my_var_get():
    return my_var # Getter must return a value.

정의할 때, Setter 혹은 Getter 함수, 둘 중 하나가 생략되어도도 됩니다:

# Only a setter.
var my_var = 5 setget my_var_set
# Only a getter (note the comma).
var my_var = 5 setget ,my_var_get

Setter와 Getter는 tool 스크립트나 플러그인에서, 입력을 확인하기 위해 편집기로 변수를 내보내기에 유용합니다.

말했듯이 지역(Local) 접근은 Setter와 Getter를 작동하지 않을 것입니다. 이 예제로 설명하겠습니다:

func _init():
    # Does not trigger setter/getter.
    my_integer = 5
    print(my_integer)

    # Does trigger setter/getter.
    self.my_integer = 5
    print(self.my_integer)

Tool mode(도구 모드)

기본적으로 스크립트는 편집기 내에서 실행하지 않으며, 오직 내보낸 속성만 편집기에서 변경할 수 있습니다. 하지만 몇 몇 경우에는, 편집기 내에서 스크립트가 실행되는 것이 유용할 때가 있습니다 (스크립트가 게임 코드를 실행하지 않거나 수동으로 실행하지 않는 경우가 있죠). 이를 위한 tool 키워드가 있고, 파일의 최상단에 배치되어야 합니다:

tool
extends Button


func _ready():
    print("Hello")

자세한 설명은 편집기에서 코드 실행하기를 참고하세요.

경고

Tool 스크립트에서 queue_free()free()로 노드를 해제할 때는 조심하세요 (특히 스크립트의 소유권이 자기 자신일 때). Tool 스크립트는 편집기에서 코드를 실행하기 때문에, 잘못 해제하면 편집기가 튕길 수 있습니다.

메모리 관리

클래스가 Reference 에서 상속할 때, 더 이상 사용하지 않게 되면 인스턴스는 해제됩니다. 가비지 콜렉터 없이, 참조만 계산됩니다. 기본적으로, 상속을 정의하지 않는 모든 클래스는 참조 를 확장합니다. 바람직하지 않다면, 클래스는 Object 를 수동으로 상속해야만 하고 instance.free()를 호출해야만 합니다. 해제할 수 없는 참조 사이클을 피하기 위해, 약한 참조를 생성하는 weakref 함수가 제공됩니다.

또는, 참조를 사용하지 않을 때, is_instance_valid(instance) 가 객체가 해제되었는 지를 확인하는데 사용될 수 있습니다.

시그널

시그널은 한 객체에서 메시지를 방출해서 메시지를 받을 수 있는 다른 객체가 받는 도구입니다. 클래스에서 맞춤 시그널을 만들려면, signal 키워드를 사용하세요.

extends Node


# A signal named health_depleted.
signal health_depleted

주석

시그널은 콜백 메커니즘입니다. 그리고 일반적인 프로그래밍 패턴인 옵저버(Observer) 규칙도 있습니다. 자세한 정보는 Game Programming Patterns의 전자책, Observer tutorial을 참고해주세요.

맞춤 시그널도 ButtonRigidBody와 같은 노드에 내장된 시그널을 연결하는 방법과 같은 방식으로 메서드에 연결할 수 있습니다.

아래의 예제에서, Character 노드의 health_depleted 시그널을 Game 노드에 연결했습니다. Character 노드가 시그널을 방출하면, Game 노드의 _on_Character_health_depleted가 호출됩니다:

# Game.gd

func _ready():
    var character_node = get_node('Character')
    character_node.connect("health_depleted", self, "_on_Character_health_depleted")


func _on_Character_health_depleted():
    get_tree().reload_current_scene()

하나의 시그널에 원하는 만큼 많은 인수를 함께 방출할 수 있습니다.

어디서 이 점이 유용하게 쓰이는 지 예제와 함께 알아봅시다. 한 번 상상해봅시다. 체력 막대가 필요합니다. 체력이 변하면 체력 막대도 따라서 변합니다. 하지만 씬 트리에서 플레이어와 사용자 인터페이스는 분리되어 있어야 합니다.

Character.gd 스크립트에서 health_changed 시그널을 정의했습니다. 그리고 Object.emit_signal()로 방출합니다. 그리고 앁 트리에서 가장 높이 있는 Game 노드에서 Object.connect() 메서드를 사용해 시그널을 Lifebar에 연결합니다:

# Character.gd

...
signal health_changed


func take_damage(amount):
    var old_health = health
    health -= amount

    # We emit the health_changed signal every time the
    # character takes damage.
    emit_signal("health_changed", old_health, health)
...
# Lifebar.gd

# Here, we define a function to use as a callback when the
# character's health_changed signal is emitted.

...
func _on_Character_health_changed(old_value, new_value):
    if old_value > new_value:
        progress_bar.modulate = Color.red
    else:
        progress_bar.modulate = Color.green

    # Imagine that `animate` is a user-defined function that animates the
    # bar filling up or emptying itself.
    progress_bar.animate(old_value, new_value)
...

주석

시그널을 쓰려면, 클래스가 Object 클래스나 다른 확장하는 클래스, Node, KinematicBody, Control 등을 확장(extend)해야 합니다.

Game 노드에는 CharacterLifebar 노드 둘 다 갖고 있습니다. Character를 연결(Connect)하고 시그널을 리시버로 방출합니다. 이 경우는 Lifebar가 해당하겠죠.

# Game.gd

func _ready():
    var character_node = get_node('Character')
    var lifebar_node = get_node('UserInterface/Lifebar')

    character_node.connect("health_changed", lifebar_node, "_on_Character_health_changed")

이렇게 하면 LifebarCharacter 노드와 묶지 않고도 체력 변화에 반응할 수 있습니다.

시그널을 정의한 후에, 개별적으로 괄호 안에 인수 이름을 적을 수 있습니다:

# Defining a signal that forwards two arguments.
signal health_changed(old_value, new_value)

인수는 편집기의 노드(Node) 독에 보입니다. 그리고 Godot는 이 인수로 콜백 함수를 생성할 수 있습니다. 하지만 시그널을 방출할 때 여전히 많은 수의 인수를 방출할 수 있습니다. 다시 말해 올바른 값을 방출하는 일은 당신에게 달린 것입니다.

../../../_images/gdscript_basics_signals_node_tab_1.png

GDScript는 시그널과 메서드 간 연결을 위해 배열의 값에 바인딩할 수 있습니다. 시그널을 방출할 때, 콜백 메서드는 바운드 값을 받습니다. 이 바운드 인수는 각 연결에서 유일하며, 값도 똑같이 유지됩니다.

이 배열의 값으로 연결에 여분의 상수 정보를 추가할 수 있습니다. 시그널이 방출됐지만 필요한 데이터로 접근하지 못할 수도 있으니까요.

위의 예제를 바탕으로, 화면에서 각 캐릭터가 받은 대미지의 로그를 표시해봅시다. 예를 들면 Player1은 22의 대미지를 받았다.같이 말이죠. health_changed 시그널은 대미지를 받는 Character의 이름을 주고 있지 않습니다. 따라서 시그널을 게임 내 콘솔에 연결하면, 바인딩 배열 인수에 Character의 이름을 추가할 수 있습니다:

# Game.gd

func _ready():
    var character_node = get_node('Character')
    var battle_log_node = get_node('UserInterface/BattleLog')

    character_node.connect("health_changed", battle_log_node, "_on_Character_health_changed", [character_node.name])

BattleLog 노드는 바인딩 배열의 각 요소를 추가 인수로 받습니다:

# BattleLog.gd

func _on_Character_health_changed(old_value, new_value, character_name):
    if not new_value <= old_value:
        return

    var damage = old_value - new_value
    label.text += character_name + " took " + str(damage) + " damage."

yield를 가진 코루틴(Coroutine)

GDScript는 yield 내장 함수를 통해 코루틴(Coroutines)을 지원합니다. yield()를 호출하면 즉시 현재 함수에서 같은 함수의 현재 상태를 반환합니다. 그리고 resume을 결과 객체에 호출하면 실행이 계속되고 함수가 반환하는 값을 반환합니다. 다시 시작하면 상태 객체는 무효화됩니다. 여기 예제가 있습니다:

func my_func():
    print("Hello")
    yield()
    print("world")


func _ready():
    var y = my_func()
    # Function state saved in 'y'.
    print("my dear")
    y.resume()
    # 'y' resumed and is now an invalid state.

이렇게 프린트됩니다:

Hello
my dear
world

yield()resume() 사이에 값을 전달할 수도 있습니다. 예를 들면:

func my_func():
    print("Hello")
    print(yield())
    return "cheers!"


func _ready():
    var y = my_func()
    # Function state saved in 'y'.
    print(y.resume("world"))
    # 'y' resumed and is now an invalid state.

이렇게 프린트됩니다:

Hello
world
cheers!

Remember to save the new function state, when using multiple yields:

func co_func():
    for i in range(1, 5):
        print("Turn %d" % i)
        yield();


func _ready():
    var co = co_func();
    while co is GDScriptFunctionState && co.is_valid():
        co = co.resume();

코루틴 & 시그널

yield의 진정한 힘은 시그널과 결합할 때 나타납니다. yield는 두 개의 인수를 받을 수 있습니다.하나는 객체, 다른 하나는 시그널이죠. 시그널을 받으면 실행이 다시 시작됩니다. 여기 몇 가지 예제가 있습니다:

# Resume execution the next frame.
yield(get_tree(), "idle_frame")

# Resume execution when animation is done playing.
yield(get_node("AnimationPlayer"), "animation_finished")

# Wait 5 seconds, then resume execution.
yield(get_tree().create_timer(5.0), "timeout")

코루틴은 무효 상태로 전환할 때 complated 시그널을 사용합니다. 예를 들어:

func my_func():
    yield(button_func(), "completed")
    print("All buttons were pressed, hurray!")


func button_func():
    yield($Button0, "pressed")
    yield($Button1, "pressed")

my_func은 오직 두 버튼이 한번에 눌릴 때 계속 실행됩니다.

You can also get the signal's argument once it's emitted by an object:

# Wait for when any node is added to the scene tree.
var node = yield(get_tree(), "node_added")

If you're unsure whether a function may yield or not, or whether it may yield multiple times, you can yield to the completed signal conditionally:

func generate():
    var result = rand_range(-1.0, 1.0)

    if result < 0.0:
        yield(get_tree(), "idle_frame")

    return result


func make():
    var result = generate()

    if result is GDScriptFunctionState: # Still working.
        result = yield(result, "completed")

    return result

This ensures that the function returns whatever it was supposed to return regardless of whether coroutines were used internally. Note that using while would be redundant here as the completed signal is only emitted when the function didn't yield anymore.

Onready 키워드

노드를 쓸 때, 씬의 일부를 변수로 참조하는 것이 일반적입니다. 씬은 활성 씬 트리에 들어갈 때만 구성하기 때문에, 하위 노드는 Node._ready()로의 호출이 있을 때만 가져올 수 있습니다.

var my_label


func _ready():
    my_label = get_node("MyLabel")

이 작업은 특히, 노드와 외부 참조가 쌓이다 보면 번거로울 수 있습니다. 이를 위해 GDScript는 onready 키워드를 갖고 있습니다. 이 키워드로 멤버 변수의 초기화는 _ready()를 호출할 때까지 지연합니다. 위의 코드를 한 줄로 대체가 가능합니다:

onready var my_label = get_node("MyLabel")

Assert 키워드

assert 키워드는 디버그 빌드에서 상태를 확인할 때 사용할 수 있습니다. 디버그 빌드가 아니면 assert 키워드는 무시됩니다. 즉, 인수로 보낸 명령문은 출시 모드로 내보낸 프로젝트에서 평가하지 않습니다. 이 때문에, assert는 반드시 역효과를 일으킬 소지가 있는 표현식을 가질 수 없습니다. 그렇지 않으면, 스크립트는 프로젝트가 디버그 빌드인지 여부에 따라 매우 다르게 동작할 것입니다.

# Check that 'i' is 0. If 'i' is not 0, an assertion error will occur.
assert(i == 0)

편집기에서 프로젝트를 실행할 때, assert 오류가 발생하면 프로젝트는 일시정지합니다.