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

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

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

새로운 언어 기능을 사용하는 위치와 방법은 당신에게 달렸습니다: 일부 민감한 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. Built-in
  2. 핵심 클래스와 노드 (Object, Node, Area2D, Camera2D, 등등)
  3. 당신이 만들고 수정한 클래스들. 에디터에서 타입을 등록하려면 새로운 클래스이름 특징을 보세요.

주석

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

커스텀 변수 타입

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

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

The second method is to use the class_name keyword when you create. For the example above, your Rifle.gd would look like this:

extends Node2D
class_name Rifle

If you use class_name, Godot registers the Rifle type globally in the editor, and you can use it anywhere, without having to preload it into a constant:

var my_rifle: Rifle

변수 캐스팅(casting)

형 변환은 타입 언어에서의 주요된 개념입니다. 어떤 값의 타입을 다른 타입으로 바꾸는 것이지요.

Imagine an Enemy in your game, that extends Area2D. You want it to collide with the Player, a KinematicBody2D with a script called PlayerController attached to it. You use the on_body_entered signal to detect the collision. With typed code, the body you detect is going to be a generic PhysicsBody2D, and not your PlayerController on the _on_body_entered callback.

You can check if this PhysicsBody2D is your Player with the as casting keyword, and using the colon : again to force the variable to use this type. This forces the variable to stick to the PlayerController type:

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.

주석

If you try to cast with a built-in type and it fails, Godot will throw an error.

안전 라인

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

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.

You can use casting to tell Godot the type you expect when you get a node: ($Timer as Timer), ($Player as KinematicBody2D), etc. Godot will ensure the type works and if so, the line number will turn green at the left of the script editor.

Safe vs Unsafe Line

Safe vs Unsafe Line

주석

You can turn off safe lines or change their color in the editor settings.

Define the return type of a function with the arrow ->

To define the return type of a function, write a dash and a right angle bracket -> after it’s declaration, followed by the return type:

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

The type void means the function does not return anything. You can use any type, as with variables:

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

You can also use your own nodes as return types:

# 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 or dynamic: stick to one style

Typed GDScript and dynamic GDScript can coexist in the same project. But I recommended 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

And with static typing:

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

And the same callback, with type hints:

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

You’re free to replace, e.g. the PhysicsBody2D, 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)

The warning system complements typed GDScript. It’s here to help you avoid mistakes that are hard to spot during development, and that may lead to runtime errors.

You can configure warnings in the Project Settings under a new section called GDScript:

warning system project settings

경고 시스템 프로젝트 설정

You can find a list of warnings for the active GDScript file in the script editor’s status bar. The example below has 3 warnings:

warning system example

경고 시스템 예제

To ignore specific warnings in one file, insert a special comment of the form #warning-ignore:warning-id, or click on the ignore link to the right of the warning’s description. Godot will add a comment above the corresponding line and the code won’t trigger the corresponding warning anymore:

warning system ignore example

경고 시스템 무시 예제

Warnings won’t prevent the game from running, but you can turn them into errors if you’d like. This way your game won’t compile unless you fix all warnings. Head to the GDScript section of the Project Settings to turn on this option. Here’s the same file as the previous example with warnings as errors turned on:

warnings as errors

warnings as errors

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

Summary

Typed GDScript is a powerful tool. Available as of version 3.1 of Godot, it helps you write more structured code, avoid common errors, and create scalable systems. In the future, static types will also bring you a nice performance boost thanks to upcoming compiler optimizations.