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

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

  • GDScript에서 타입을 사용하는 방법
  • 정적 타입은 버그를 피하는데 도움을 줍니다

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

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

주석

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

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

타입 GDScript로, 코드를 작성할 수록 훨씬 더 많은 오류를 Godot가 감지할 수 있습니다! 즉, 메서드를 호출할 때, 인수의 타입이 나타나기 때문에 작업하는 동안 당신과 당신의 동료에게 더 많은 정보를 제공합니다.

인벤토리 시스템을 프로그래밍 한다고 상상해보세요. 당신은 Item 노드를 코딩하고, Inventory를 코딩합니다. 아이템은 인벤토리에 넣기 위해, 당신의 코드로 작업하는 사람들은 항상 ItemInventory.add으로 전달해야 합니다. 타입으로는, 다음을 시행할 수 있습니다:

# 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는 당신이 코딩을 하는 중에 바로 경고를 보여줍니다: 엔진은 실행 중에 문제가 될 수 있는 코드 부분을 식별하지만 당신이 코드를 그대로 둘지 말지는 당신이 결정할 수 있도록 합니다. 잠시후에 더 설명합니다.

정적 타입 또한 더 나은 코드 완성 옵션을 제공합니다. 아래에서``PlayerController`` 라는 클래스에 대한 동적 및 정적 타입 완료 옵션의 차이점을 볼 수 있습니다.

당신은 아마 전에 변수에 노드를 저장하고, 변수이름 뒤에 점을 입력했으나 자동완성 제안이 표시되지 않았던 경험이 있었을 수 있습니다:

code completion options for dynamic

이는 동적 코드 때문입니다. Godot는 함수에 전달하는 노드나 값 타입을 알 수 없습니다. 그러나 유형을 명시적으로 작성해주면, 노드에서 모든 공용 메서드 및 변수를 가져옵니다:

code completion options for typed

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

전체적으로, 타입형 프로그래밍은 더 구조화된 경험을 제공합니다. 에러를 방지하고 스크립트가 자체문서화되도록 돕습니다. 이러한 점은 긴 프로젝트를 하거난 팀 프로젝트에서 유용합니다: 연구는 개발자들이 다른 개발자의 코드나 또는 자신이 오래전에 작성하여 내용을 잊어버린 스크립트를 보는데 대부분의 시간을 보낸다고 합니다. 코드가 더 명확하고 구조화될수록 이해하는 것은 더 빨라지며 더 빨리 진도를 나갈 수 있습니다.

정적 타입 사용법

변수나 상수의 형(type)을 정의하기 위해서, 변수이름뒤에 콜론을 붙이고 타입을 명시하세요. 예를 들면 "var health:int". 이것은 변수의 타입이 항상 동일하도록 강제합니다:

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

커스텀 타입을 다루고 있으니, bodyPlayerController를 확장하지 않는다면, player변수는 null로 설정될 것입니다. 이걸로 바디가 플레이어인지 아닌지 확인할 수 있습니다. 또한 캐스트 덕분에 플레이어 변수에 완전한 자동 완성을 얻을 수 있습니다.

주석

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

안전 라인

안전 라인을 보장하기 위해 형 변환을 사용할 수도 있습니다. 안전 라인은 애매모호한 코드가 형안전한지 알려주는 Godot 3.1에서 새로 추가된 도구입니다. 정적 타입과 동적 타입을 섞어서 대입시킬 수 있기 때문에 Godot은 런타임에 오류가 발생할 지에 대한 여부를 판명할 충분한 정보가 없습니다.

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

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

Safe vs Unsafe Line

안전 vs 불안전 라인

주석

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

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

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

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라고 하는 섹션에서 경고를 구성할 수 있습니다:

warning system project settings

경고 시스템 프로젝트 설정

스크립트 편집기의 상태 바에서 활성화된 GDScript 파일을 위한 경고 목록을 찾을 수 있습니다. 밑의 예제는 3개의 경고를 갖습니다:

warning system example

경고 시스템 예제

하나의 파일에서 특정 경고를 무시하려면, 특수 주석을 끼워 넣어야 합니다 #warning-ignore:warning-id, 혹은 경고의 설명 오른쪽에 무시 링크를 클릭합니다. Godot는 위에 주석을 추가해야 해당하는 줄과 코드가 더 이상 같은 경고를 내지 않도록 합니다:

warning system ignore example

경고 시스템 무시 예제

경고는 게임 실행을 방해하지 않지만, 원한다면 오류로 바꿀 수 있습니다. 이 방법으로 모든 오류를 고치지 않는다면 게임이 컴파일 되지 않을 것입니다. 프로젝트 설정의 GDScript 섹션으로 가서 이 설정을 킵니다. 이전 예제와 같은 파일로 경고가 오류로 바뀐 것입니다:

warnings as errors

오류와 같은 경고

타입을 지정할 수 없는 경우

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

이넘(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 버전에 이용할 수 있으며, 일반적인 오류들을 피하면서, 더 구조화된 코드와 확장성 있는 체계를 만드는 것을 도와줍니다. 미래에서는 정적 타입도 향후 컴파일러 최적화로 좋은 퍼포먼스 향상을 제공할 것입니다.