스크립팅(Scripting)

소개

Godot 3.0 이전까지는 스크립트하는 방법이 GDScript밖에 없었습니다. 현재 Godot는 4개를 (그래요, 4개!) 공식 언어로 갖고 있고 별개로 스크립트 언어를 추가하는 기능도 있습니다!

이것은 대단합니다. 왜냐하면 큰 유연성을 제공해주기 때문입니다. 하지만 우리가 여러가지 언어를 지원하는 것을 더 어렵게 만들기도 합니다.

Godot에서 "메인" 언어는 GDScript와 VisualScript입니다. 이들이 선택된 주된 이유는 Godot와 결합 능력이 좋기 때문입니다. 둘 다 기본 편집기에서 매끄럽게 사용할 수 있습니다. 반면 C#과 C++는 별도의 IDE에서 편집해야 합니다. 당신이 정적 타입 언어를 좋아한다면 C#과 C++을 선택하세요.

GDScript

GDScript는 앞에서 말하듯이, Godot에서 사용되는 메인 언어입니다. 이 언어는 다른 언어들에 비해 Godot와 높은 결합으로 얻는 장점이 있습니다:

  • 간단합니다. 우아합니다. 그리고 디자인 면에서도 Lua, Python, Squirrel 등과 같은 다른 언어들과 비슷합니다.
  • 매우 빠르게 불러오고 컴파일합니다.
  • 편집기 결합 능력은 노드, 시그널, 그리고 많은 편집 중인 씬과 관련된 항목을 위한 코드 완성으로 인해 즐거움을 줍니다.
  • (Vectors, transforms 등과 같은) 많은 벡터 유형을 지니고 있어서, 선형 대수를 많이 사용하는데 효율적입니다.
  • 정적 타입 언어처럼 효율적인 여러 스레드를 지원합니다 - 이것이 Lua, Squirrel 등의 가상 머신을 피하게 만든 제한 중 하나 입니다.
  • 쓰레기 수집을 쓰지 않습니다. 때문에 결정론적으로 인한 약간의 자동화를 교환합니다 (어쨌든 대부분의 오브젝트들은 참조 카운트됩니다).
  • 더 많은 성능이 필요하다면 (GDNative를 통해) C++의 코드 섹션을 쉽게 최적화할 수 있습니다. 엔진을 전부 다시 컴파일 하지 않아도 됩니다.

아직 프로그래밍에 경험이 없고 특히 동적 타입 언어를 정하지 않았다면 GDScript로 시작해보세요!

VisualScript

3.0을 시작하면서, Godot는 Visual Scripting을 제공합니다. "블록과 연결" 언어의 전형적인 구현 방식이지만 Godot의 작동 방식에 적용되었습니다.

Visual scripting은 비 프로그래머 뿐만 아니라 경험 있는 프로그래머도 게임 디자이너나 아티스트와 같은 사람들이 코드 일부분을 보기 쉽게 만들어 주는 멋진 도구입니다.

프로그래머는 이것으로 상태 시스템이나 맞춤 시각 노드 워크플로를 만들 수도 있습니다 - 예를 들면 대화 시스템이죠.

.NET / C#

Microsoft의 C#은 게임 개발자들 사이에서 사랑받는 언어입니다. 그래서 이것을 공식으로 지원합니다. C#은 많은 코드로 작성된 완전한 언어로, Microsoft의 자비로운 기여 덕분에 지원할 수 있게 되었습니다.

비록 쓰레기 수집에 대해 알아야 하지만 이것은 퍼포먼스와 사용의 용이성 두가지를 절충하고 있습니다.

Godot가 Mono .NET 런타임을 사용하기 때문에, 이론적으로 모든 제 3자 .NET 라이브러리나 프레임워크를 Godot의 스크립트로 사용할 수 있습니다. F#, Boo, ClojureCLR와 같은 공용 언어 인프라 호환 프로그래밍 언어 또한 가능합니다. 하지만 실제로는 C#만 공식으로 지원하는 .NET 옵션입니다.

GDNative / C++

마지막으로 3.0 출시에 가장 빛나는 추가 요소입니다: GDNative는 Godot를 다시 컴파일 하지 않아도 (심지어 재시작을 하지 않아도) C++에서 스크립트할 수 있습니다.

모든 C++버전을 사용할 수 있고, 내부 C API Bridge를 사용해서 생성한 공유 라이브러리의 컴파일러 브랜드와 버전을 완벽하게 혼합할 수 있습니다.

이 언어는 성능 분야에는 최고이지만 게임 전체에 사용할 필요는 없습니다. 다른 부분은 GDScript나 VisualScript을 통해 다른 부분을 작성할 수 있기 때문이죠. 그러나 이 API는 명확하고 사용하기 쉽습니다. 이 API의 대부분은 Godot의 실제 C++ API와 유사합니다.

더 많은 언어는 GDNative 인터페이스를 통해 사용할 수 있지만 이 인터페이스에 대한 공식적인 지원은 없습니다.

씬 스크립트하기

본 튜토리얼의 나머지 부분에서는 Button과 Label로 이루어진 GUI 씬을 설정합니다. 여기서 Button을 누르면 Label이 업데이트됩니다. 이를 통해 다음을 설명합니다:

  • 스크립트를 작성하여 노드에 붙이기.
  • 시그널을 통해 UI 요소들을 연결하기.
  • 씬의 다른 노드에 접근할 수 있는 스크립트를 작성하기.

계속하기 전에 GDScript 참조를 반드시 읽어 보기 바랍니다. 이 언어는 간단하게 설계되었고, 참조가 짧기 때문에 개념을 파악하는 데 몇 분 정도 밖에 걸리지 않습니다.

씬 설정

씬 탭에서 "자식 노드 추가하기(Add Child Node)" 대화 상자를 사용해서 (혹은 Ctrl+A 키로) 다음 노드로 이루어진 계층 구조를 만듭니다:

  • Panel
    • Label
    • Button

씬 트리는 다음과 같아야 합니다:

../../_images/scripting_scene_tree.png

2D 편집기로 Button과 Label을 아래 이미지와 같이 배치하고 크기를 조정합니다. 인스펙터 탭에서 문자를 설정할 수 있습니다.

../../_images/label_button_example.png

마지막으로 sayhello.tscn와 같은 이름으로 씬을 저장합니다.

스크립트 추가하기

Panel 노드에서 우클릭합니다. 우클릭 메뉴에서 "스크립트 붙이기(Attach Script)"를 선택합니다:

../../_images/add_script.png

스크립트 생성 대화 상자가 나타납니다. 여기서 스크립트 언어, 클래스 이름 및 기타 관련 옵션을 설정할 수 있습니다.

GDScript에서 파일 자체는 클래스를 나타냅니다. 따라서 클래스 이름(Class Name) 칸은 수정할 수 없습니다.

스크립트를 붙이는 노드는 Panel입니다. 따라서 상속(Inherits) 칸은 자동으로 "Panel"로 채워집니다. 이것이 우리의 목표입니다. 스크립트의 목적은 Panel 노드의 기능을 확장하는 것이죠.

마지막으로 스크립트 경로(Path) 이름을 입력하고 만들기(Create)를 선택합니다:

../../_images/script_create.png

이제 스크립트가 만들어지고 노드에 추가됩니다. 스크립트가 추가된 것은 노드 옆의 "스크립트 열기(Open script)" 아이콘으로 확인할 수 있습니다. 인스펙터(Inspector) 아래의 Script 속성에서도 확인할 수 있습니다:

../../_images/script_added.png

스크립트를 편집하려면 위 이미지에 강조 표시된 두 버튼 중 하나를 선택합니다. 그러면 기본 템플릿으로 된 스크립트 편집기로 이동합니다:

../../_images/script_template.png

안에 많은 내용이 있지는 않습니다. _ready() 함수는 노드와 노드의 모든 자식이 활성 씬으로 들어갈 때 호출됩니다. 참고: _ready() 는 생성자가 아닙니다. 생성자는 _init()입니다.

스크립트의 역할

스크립트는 노드에 행위를 추가합니다. 스크립트는 노드가 작동하는 방식 말고도, 자식, 부모, 형제 등과 같은 다른 노드와 상호 작용하는 방식을 제어합니다. 스크립트의 지역 범위는 노드입니다. 다시 말해, 스크립트는 노드가 제공하는 함수에 속합니다.

../../_images/brainslug.jpg

시그널(Signal) 다루기

시그널(Signal)은 특정 행동을 할 때 "방출(emit)"합니다. 그리고 시그널은 스크립트 인스턴스의 모든 함수에 연결될 수 있습니다. 시그널은 대부분 GUI 노드에서 사용됩니다. 하지만 다른 노드에도 시그널이 있고, 스크립트에서 맞춤 시그널을 정의할 수도 있습니다.

이 단계에서, "pressed" 시그널을 맞춤 함수에 연결할 것입니다. 먼저 연결을 형성하고, 그런 다음 맞춤 함수를 정의합니다. Godot에서는 연결을 만드는 두 가지 방법을 제공합니다: 편집기의 시각적 인터페이스를 통해 연결하거나, 코드를 통해 연결합니다.

이 튜토리얼 시리즈의 나머지에서는 코드 메서드를 사용하겠습니다. 그러나 나중에 이것을 보고 참조할 수 있도록 어떻게 편집기 인터페이스가 작동하는지 알아봅시다.

씬 트리에서 Button 노드를 고르고 "노드(Node)" 탭을 선택하세요. 그런 다음 "시그널(Signals)"이 선택되어 있는지 확인하세요.

../../_images/signals.png

"BaseButton" 아래에 있는 "pressed()"를 고르고 오른쪽 아래 "연결하기...(Connect...)" 버튼을 누릅니다. 연결 만들기 대화 상자가 열립니다.

../../_images/connect_dialogue.png

The top of the dialogue displays a list of your scene's nodes with the emitting node's name highlighted in blue. Select the "Panel" node here.

The bottom of the dialogue shows the name of the method that will be created. By default, the method name will contain the emitting node's name ("Button" in this case), resulting in _on_[EmitterNode]_[signal_name].

그럼 이것으로 시각적 인터페이스를 사용하는 법에 관한 강좌를 마칩니다. 하지만 이것은 스크립팅 튜토리얼입니다. 학습을 위해 수작업 과정을 알아봅시다!

이 튜토리얼을 달성하기 위해, 우리는 Godot 프로그래머들이 가장 잘 사용하는 함수를 소개하겠습니다: Node.get_node(). 이 함수는 스크립트가 있는 노드 기준 상대적인 경로를 사용해서 씬에 있는 노드를 가져옵니다.

편의상 extend Panel 아래에 있는 모든 것을 지우세요. 나머지는 수동으로 채울 것입니다.

스크립트를 붙일 때 Button과 Label은 Panel 아래에 나란히 있습니다. 따라서 _ready() 함수 아래에 다음과 같이 입력해서 Button을 가져올 수 있습니다:

func _ready():
    get_node("Button")
public override void _Ready()
{
    GetNode("Button");
}

다음으로 버튼이 눌릴 때 호출하는 함수를 작성합니다:

func _on_Button_pressed():
    get_node("Label").text = "HELLO!"
public void _OnButtonPressed()
{
    GetNode<Label>("Label").Text = "HELLO!";
}

마지막으로 버튼의 "pressed" 시그널을 _ready()에 연결하도록 Object.connect()를 사용합니다.

func _ready():
    get_node("Button").connect("pressed", self, "_on_Button_pressed")
public override void _Ready()
{
    GetNode("Button").Connect("pressed", this, nameof(_OnButtonPressed));
}

최종 스크립트는 다음과 같아야 합니다:

extends Panel

func _ready():
    get_node("Button").connect("pressed", self, "_on_Button_pressed")

func _on_Button_pressed():
    get_node("Label").text = "HELLO!"
using Godot;

// IMPORTANT: the name of the class MUST match the filename exactly.
// this is case sensitive!
public class sayhello : Panel
{
    public override void _Ready()
    {
        GetNode("Button").Connect("pressed", this, nameof(_OnButtonPressed));
    }

    public void _OnButtonPressed()
    {
        GetNode<Label>("Label").Text = "HELLO!";
    }
}

씬을 실행하고 버튼을 누르면 다음과 같은 결과를 얻을 수 있습니다:

../../_images/scripting_hello.png

어머, 안녕하세요! 첫번째 씬 스크립팅을 축하해요.

주석

이 튜토리얼에 관한 일반적인 오해로 get_node(path)가 작동하는 방식이 있습니다. 주어진 노드는 get_node(path)가 바로 아래의 자식을 찾습니다. 위 코드에서 Button은 Panel의 자식이라는 것이죠. 만일 버튼이 Panel이 아니라 Label의 자식이었다면, 나오는 코드는 다음과 같을 것입니다:

# Not for this case,
# but just in case.
get_node("Label/Button")
// Not for this case,
// but just in case.
GetNode("Label/Button")

또한 노드는 이름으로 참조됩니다. 유형이 아닙니다.

주석

연결 대화 상자의 오른쪽 패널은 특정 값을 연결된 함수의 매개변수에 묶기 위한 것입니다. 다른 유형의 값을 추가하거나 제거할 수 있습니다.

코드에서도 기본적으로 비어 있는 4번째 배열(Array) 매개변수를 사용하면 가능합니다. 더 많은 정보를 원한다면 언제든지 Object.connect 메서드를 읽어보세요.