GDScript에서 정적 타이핑(Static typing)

이 가이드에서 다음 내용을 배울 것입니다:

  • GDScript에서 타입을 사용하는 방법

  • 정적 타입은 버그를 피하는데 도움을 줍니다

새로운 언어 기능을 사용하는 위치와 방법은 당신에게 달렸습니다: 일부 민감한 GDScript 파일에서만 이것을 사용할 수 있고, 어디에든 사용할 수 있고, 항상 그래왔듯이 코드를 작성할 수 있습니다!

정적 타입은 변수, 상수, 함수, 매개 변수, 그리고 반환 타입에 사용될 수 있습니다.

참고

타입 GDScript는 Godot 3.1부터 사용할 수 있습니다.

정적 타이핑에 대한 간단한 설명

With typed GDScript, Godot can detect even more errors as you write code! It gives you and your teammates more information as you're working, as the arguments' types show up when you call a method.

Imagine you're programming an inventory system. You code an Item node, then an Inventory. To add items to the inventory, the people who work with your code should always pass an Item to the Inventory.add method. With types, you can enforce this:

# In 'Item.gd'.
class_name Item
# In 'Inventory.gd'.
class_name Inventory


func add(reference: Item, amount: int = 1):
    var item = find_item(reference)
    if not item:
        item = _instance_item_from_db(reference)

    item.amount += amount

타입 GDScript의 또 다른 두드러진 장점은 새로운 **경고 시스템**입니다. 3.1버전부터 Godot는 당신이 코딩을 하는 중에 바로 경고를 보여줍니다: 엔진은 실행 중에 문제가 될 수 있는 코드 부분을 식별하지만 당신이 코드를 그대로 둘지 말지는 당신이 결정할 수 있도록 합니다. 잠시후에 더 설명합니다.

Static types also give you better code completion options. Below, you can see the difference between a dynamic and a static typed completion options for a class called PlayerController.

You've probably stored a node in a variable before, and typed a dot to be left with no autocomplete suggestions:

code completion options for dynamic

This is due to dynamic code. Godot cannot know what node or value type you're passing to the function. If you write the type explicitly however, you will get all public methods and variables from the node:

code completion options for typed

향후에, 타입형 GDScript는 또한 코드 실행 속도 등의 성능을 개선할 것입니다: 즉석(Just-In-Time) 컴파일과 다른 컴파일러 성능 향상이 이미 로드맵에 대기 중입니다!

Overall, typed programming gives you a more structured experience. It helps prevent errors and improves the self-documenting aspect of your scripts. This is especially helpful when you're working in a team or on a long-term project: studies have shown that developers spend most of their time reading other people's code, or scripts they wrote in the past and forgot about. The clearer and the more structured the code, the faster it is to understand, the faster you can move forward.

정적 타입 사용법

To define the type of a variable or a constant, write a colon after the variable's name, followed by its type. E.g. var health: int. This forces the variable's type to always stay the same:

var damage: float = 10.5
const MOVE_SPEED: float = 50.0

Godot은 당신이 콜론을 입력했지만 타입을 생략한 경우 타입을 예측하기 위해서 노력합니다:

var life_points := 4
var damage := 10.5
var motion := Vector2()

현재 세 개의 타입을 사용할 수 있습니다:

  1. 내장 타입

  2. 핵심 클래스와 노드 (Object, Node, Area2D, Camera2D, 등등)

  3. 당신이 만들고 수정한 클래스들. 편집기에서 타입을 등록하려면 새로운 클래스이름 특징을 보세요.

참고

상수에 대해서는 Godot가 자동으로 대입된 값으로부터 타입을 인식하기 때문에 타입 힌트를 쓸 필요가 없습니다. 하지만 여전히 코드의 의도를 명확히 하려면 그렇게 하는게 좋습니다.

맞춤 변수 타입

당신이 만든 클래스를 포함해 어떤 클래스든지 타입으로 사용할 수 있습니다. 스크립트에서 쓸 수 있는 방법이 두 가지가 있는데, 첫번째 방법은 타입으로 사용할 스크립트를 미리 로딩하여 상수로 저장하는 것입니다:

const Rifle = preload("res://player/weapons/Rifle.gd")
var my_rifle: Rifle

두 번째 방법은 생성할 때 class_name 키워드를 사용하는 것입니다. 위의 예제에서 Rifle.gd는 다음과 같습니다:

extends Node2D
class_name Rifle

class_name을 사용한다면, Godot는 Rifle 타입을 편집기에서 전역으로 등록합니다, 그리고 상수에 미리 불러올 필요 없이 어디에나 그것을 쓸 수 있습니다:

var my_rifle: Rifle

변수 캐스팅(casting)

타이프 캐스팅(Type casting)은 타입 언어에서의 주요된 개념입니다. 어떤 값의 타입을 다른 타입으로 바꾸는 것입니다.

게임에 Area2D를 확장하는 적이 있다고 상상합시다. 거기에 PlayerController 스크립트가 붙은 KinematicBody2D, 즉 플레이어가 충돌해야 합니다. on_body_entered 시그널을 사용하면 콜리전을 감지할 수 있습니다. 타입형 코드로, 감지한 바디는 일반 PhysicsBody2D이 될 것이며, _on_body_entered 콜백의 PlayerController가 아닐 것입니다.

PhysicsBody2Das 캐스팅 키워드로 이루어진 플레이어인지 확인할 수 있습니다, 그리고 콜론 :을 다시 사용해 변수가 이 타입을 사용하도록 강제합니다. 이렇게 하면 변수는 PlayerController 타입에 고정하게 합니다:

func _on_body_entered(body: PhysicsBody2D) -> void:
    var player := body as PlayerController
    if not player:
        return

    player.damage()

As we're dealing with a custom type, if the body doesn't extend PlayerController, the playervariable will be set to null. We can use this to check if the body is the player or not. We will also get full autocompletion on the player variable thanks to that cast.

참고

내장 타입으로 캐스트를 하려 하다가 실패한다면, Godot는 오류를 내보낼 것입니다.

안전 라인

안전 라인을 보장하기 위해 타입 변환을 사용할 수도 있습니다. 안전 라인은 Godot 3.1에 새로 추가된 도구인데, 애매모호한 코드가 타입 안전한지 알려줍니다. 스크립트에서 정적 타입과 동적 타입을 섞어서 사용할 수 있기 때문에 Godot가 이 명령이 런타임에 오류가 발생시킬지 판단하기에는 정보가 충분하지 않습니다.

자손 노드를 얻을 때 이런 일이 발생합니다. 타이머를 예로 들어봅시다: 동적 코드를 통해, $Timer 로 노드를 얻을 수 있습니다. GDScript는 덕 타이핑 을 지원하기 때문에, 타이머가 Timer 타입에 속해 있다면, NodeObject 에도 속해있는 것입니다, 확장된 두 클래스이죠. 동적 GDScript를 사용하면 호출하는데 필요한 메서드만 가지고 있는 한 노드 타입에 대해 걱정하지 않아도 됩니다.

노드를 얻을 때 예상되는 타입을 Godot에게 알리기 위해 캐스팅을 사용할 수 있습니다: ($Timer as Timer), ($Player as KinematicBody2D)등. Godot가 타입이 올바른지 확인한 다음, 만약 올바르다면 스크립트 편집기 왼쪽의 줄 숫자가 초록색으로 바뀔 것입니다.

Unsafe vs Safe Line

비안전 라인(line 7) vs 안전 라인(line 6 and 8)

참고

편집기 설정에서 안전 라인을 끌 수 있고 혹은 색상을 바꿀 수 있습니다.

-> 화살표로 함수의 반환 타입 정의하기

함수의 반환 타입을 정의하기 위해, 하이픈과 오른쪽 직각 괄호를 적습니다 -> 선언 후, 반환 타입이 뒤따릅니다:

func _process(delta: float) -> void:
    pass

void 타입은 함수가 아무것도 반환하지 않는 것을 의미합니다. 변수와 마찬가지로 어떤 타입도 사용할 수 있습니다:

func hit(damage: float) -> bool:
    health_points -= damage
    return health_points <= 0

또한 자신의 노드를 반환 타입으로 사용할 수 있습니다:

# Inventory.gd

# Adds an item to the inventory and returns it.
func add(reference: Item, amount: int) -> Item:
    var item: Item = find_item(reference)
    if not item:
        item = ItemDatabase.get_instance(reference)

    item.amount += amount
    return item

타입형 혹은 동적: 하나의 스타일을 고수하기

타입형 GDScript와 동적 GDScript는 같은 프로젝트에 공존할 수 있습니다. 하지만 코드베이스의 일관성과 동료들을 위해, 둘 중 하나의 스타일을 고수하는 것을 권장합니다. 같은 가이드라인을 따른다면 모두가 함께 일하기 더 쉽고, 다른 사람의 코드를 읽고 이해하는데 더 빠를 것입니다.

타입형 코드는 조금 더 써야 할 것이 많지만, 위에서 말한 이득을 얻을 수 있습니다. 여기 같은, 빈 스크립트 예제가 있습니다, 동적 스타일에서는 이렇습니다:

extends Node


func _ready():
    pass


func _process(delta):
    pass

그리고 정적 타입형에서는 이렇습니다:

extends Node


func _ready() -> void:
    pass


func _process(delta: float) -> void:
    pass

보시다시피, 엔진의 가상 메서드로 타입을 사용할 수도 있습니다. 시그널 콜백도 다른 메서드처럼 타입형을 사용할 수 있습니다. 동적 스타일에서 body_entered 시그널입니다:

func _on_Area2D_body_entered(body):
    pass

그리고 타입 힌트를 쓴 같은 콜백입니다:

func _on_area_entered(area: CollisionObject2D) -> void:
    pass

예를 들어 CollisionObject2D 타입을 여러분만의 자체 타입으로 바꾸어 자동으로 매개변수를 캐스팅하도록 할 수 있습니다:

func _on_area_entered(bullet: Bullet) -> void:
    if not bullet:
        return

    take_damage(bullet.damage)

bullet 변수는 어떤 CollisionObject2D 값이든 가질 수 있지만, 여기서는 이 변수가 우리의 프로젝트에서 만든 노드인 Bullet 임을 확실히 합니다. Area2DBullet 을 확장하지 않는 다른 노드인 경우, bullet 변수는 null 이 될 것입니다.

경고 시스템(Warning system)

참고

GDScript의 경고 시스템에 대한 관한 문서는 GDScript 내보내기(Export) 로 옮겨졌습니다.

타입을 지정할 수 없는 경우

이 소개를 마무리하며, 타입형 힌트를 사용할 수 없는 몇몇 경우를 알아봅시다. 밑의 모든 예제는 오류를 유발합니다.

열거형(Enum)은 타입으로 사용할 수 없습니다:

enum MoveDirection {UP, DOWN, LEFT, RIGHT}
var current_direction: MoveDirection

배열에서 각 멤버의 타입을 지정할 수 없습니다. 그렇게 할 경우 오류를 줄 것입니다:

var enemies: Array = [$Goblin: Enemy, $Zombie: Enemy]

for 루프에서 타입의 할당을 강제할 수 없습니다, for 키워드 루프에서 각 요소는 이미 다른 타입을 갖고 있기 때문입니다. 따라서 아래와 같은 코드는 작성할 수 없습니다:

var names = ["John", "Marta", "Samantha", "Jimmy"]
for name: String in names:
    pass

두 개의 스크립트가 서로에게 의지하며 사이클을 만들 수 없습니다:

# Player.gd

extends Area2D
class_name Player


var rifle: Rifle
# Rifle.gd

extends Area2D
class_name Rifle


var player: Player

요약

타입형 GDScript는 강력한 도구입니다. Godot 3.1 버전에 이용할 수 있으며, 일반적인 오류들을 피하면서, 더 구조화된 코드와 확장성 있는 체계를 만드는 것을 도와줍니다. 미래에서는 정적 타입도 향후 컴파일러 최적화로 좋은 퍼포먼스 향상을 제공할 것입니다.