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는 오류를 내보낼 것입니다.

안전 라인

You can also use casting to ensure safe lines. Safe lines are a new tool in Godot 3.1 to tell you when ambiguous lines of code are type-safe. As you can mix and match typed and dynamic code, at times, Godot doesn't have enough information to know if an instruction will trigger an error or not at runtime.

This happens when you get a child node. Let's take a timer for example: with dynamic code, you can get the node with $Timer. GDScript supports duck-typing, so even if your timer is of type Timer, it is also a Node and an Object, two classes it extends. With dynamic GDScript, you also don't care about the node's type as long as it has the methods you need to call.

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

Unsafe vs Safe Line

Unsafe line (line 7) vs Safe Lines (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

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

Typed GDScript and dynamic GDScript can coexist in the same project. But I recommend to stick to either style for consistency in your codebase, and for your peers. It's easier for everyone to work together if you follow the same guidelines, and faster to read and understand other people's code.

Typed code takes a little more writing, but you get the benefits we discussed above. Here's an example of the same, empty script, in a dynamic style:

extends Node


func _ready():
    pass


func _process(delta):
    pass

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

extends Node


func _ready() -> void:
    pass


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

As you can see, you can also use types with the engine's virtual methods. Signal callbacks, like any methods, can also use types. Here's a body_entered signal in a dynamic style:

func _on_Area2D_body_entered(body):
    pass

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

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

You're free to replace, e.g. the CollisionObject2D, with your own type, to cast parameters automatically:

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

    take_damage(bullet.damage)

The bullet variable could hold any CollisionObject2D here, but we make sure it is our Bullet, a node we created for our project. If it's anything else, like an Area2D, or any node that doesn't extend Bullet, the bullet variable will be null.

경고 시스템(Warning system)

주석

Documentation about the GDScript warning system has been moved to GDScript warning system.

Cases where you can't specify types

To wrap up this introduction, let's cover a few cases where you can't use type hints. All the examples below will trigger errors.

You can't use Enums as types:

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

You can't specify the type of individual members in an array. This will give you an error:

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

You can't force the assignment of types in a for loop, as each element the for keyword loops over already has a different type. So you cannot write:

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

Two scripts can't depend on each other in a cyclic fashion:

# Player.gd

extends Area2D
class_name Player


var rifle: Rifle
# Rifle.gd

extends Area2D
class_name Rifle


var player: Player

요약

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