스크립팅 (계속)

처리(Process)하기

Godot에서 여러 작업은 콜백 또는 가상 함수로 조작됩니다. 그래서 항상 실행되는 코드를 작성할 필요가 없습니다.

하지만 여전히 모든 프레임에서 처리하는 스크립트가 필요한 경우가 많습니다. 처리(Process)에는 두가지 유형이 있습니다: 정적 처리(Idel Processing)와 동적 처리(Physics Processing).

정적 처리는 스크립트에서 메서드 Node._process()가 발견되면 활성화됩니다. Node.set_process()를 사용해서 이 기능을 껐다 켰다 할 수 있습니다.

이 메서드는 매 프레임마다 호출됩니다:

func _process(delta):
    # Do something...
    pass
public override void _Process(float delta)
{
    // Do something...
}

_process()가 호출되는 비중은 응용 프로그램이 초 당 몇 프레임 (FPS)으로 실행되는지에 따라 다르다는 것을 마음에 새기는 것이 중요합니다. 이 비율은 시간과 기기에 따라 다를 수 있습니다.

이 가변성을 다루는 것을 돕기 위해, _process()가 호출된 이후부터 delta 매개 변수는 경과된 시간을 초 단위, 즉 실수의 형태로 갖습니다.

이 매개 변수를 사용하면 게임의 초 당 프레임 수와 관계 없이, 항상 동일한 시간이 소요되도록 할 수 있습니다.

예를 들어 이동은 시간 delta와 곱해서 이동 속도를 일정하게 하고 프레임 비율과 무관하게 합니다.

_physics_process()를 이용한 동적 처리도 이와 유사합니다. 하지만 이것은 각 동적 단계 이전에서 발생해야 하는 과정에서 사용해야 합니다. 예를 들면 캐릭터 조종하는 것이 있죠. 이것은 항상 동적 단계 이전에 실행되며 고정된 시간 간격으로 호출됩니다: 기본은 초 당 60회. 프로젝트 설정(Project Settings)에서 Physics -> Common -> Physics Fps에서 시간 간격을 변경할 수 있습니다.

그러나 _proceed() 함수는 동적과 동기화되지 않습니다. 프레임 비율이 일정하지 않고 하드웨어와 게임 최적화에 의존하기 때문이죠. 이것은 단일 스레드 게임의 동적 단계를 거친 후에 실행됩니다.

간단하게 _process() 함수가 작동하는 것을 보려면 Label 노드 하나로 된 씬을 만들고, 다음 스크립트를 넣습니다:

extends Label

var accum = 0

func _process(delta):
    accum += delta
    text = str(accum) # 'text' is a built-in label property.
public class CustomLabel : Label
{
    private float _accum;

    public override void _Process(float delta)
    {
        _accum += delta;
        Text = _accum.ToString(); // 'Text' is a built-in label property.
    }
}

위 스크립트는 프레임 당 증가하는 카운터를 보여 줍니다.

그룹

Godot에서 그룹은 다른 소프트웨어에서 접했을 법한태그와 비슷합니다. 노드는 많은 그룹에 원하는 만큼 추가될 수 있습니다. 이는 거대한 씬을 조직하는데 유용한 기능입니다. 노드를 그룹에 추가하는 두 가지 방법이 있습니다. 먼저 첫 번째는 UI에서 하는 것입니다. 노드(Node) 패널 하단의 그룹(Groups) 버튼을 사용합니다:

../../_images/groups_in_nodes.png

두 번째 방법은 코드에서 하는 것입니다. 다음 스크립트는 현재 노드가 씬 트리에 나타나면 enemies라는 그룹에 노드가 추가되는 스크립트입니다.

func _ready():
    add_to_group("enemies")
public override void _Ready()
{
    base._Ready();

    AddToGroup("enemies");
}

이렇게 하면 플레이어가 비밀 기지에 몰래 들어간 것이 발각될 경우, SceneTree.call_group()을 사용하여 모든 적이 경보가 울린다는 사실을 인지할 수 있습니다:

func _on_discovered(): # This is a purely illustrative function.
    get_tree().call_group("enemies", "player_was_discovered")
public void _OnDiscovered() // This is a purely illustrative function.
{
    GetTree().CallGroup("enemies", "player_was_discovered");
}

위의 코드는 그룹 enemies의 모든 구성원이``player_was_discovered`` 함수를 호출합니다.

또한 SceneTree.get_nodes_in_group()을 호출하여 enemies 노드의 전체 목록을 가져올 수도 있습니다:

var enemies = get_tree().get_nodes_in_group("enemies")
var enemies = GetTree().GetNodesInGroup("enemies");

SceneTree 클래스는 씬끼리, 노드 계층끼리, 그리고 노드의 그룹끼리 상호작용하는 많은 유용한 메서드를 제공합니다. 이것으로 쉽게 씬을 바꾸거나 다시 불러올 수 있고, 게임을 끄거나 일시정지를 켜고 끄는 행위를 할 수 있습니다. 심지어 흥미로운 시그널도 함께 제공됩니다. 시간이 된다면 한번 확인해보세요!

알림(Notification)

Godot에는 알림 시스템이 있습니다. 보통은 스크립팅에 쓰이지 않습니다. 너무 로우 레벨이며 대부분은 가상 함수에서 제공하기 때문이죠. 그냥 존재한다는 것 만으로도 좋은 일입니다. 예를 들어 스크립트에 Object._notification() 함수를 추가할 수 있습니다:

func _notification(what):
    match what:
        NOTIFICATION_READY:
            print("This is the same as overriding _ready()...")
        NOTIFICATION_PROCESS:
            print("This is the same as overriding _process()...")
public override void _Notification(int what)
{
    base._Notification(what);

    switch (what)
    {
        case NotificationReady:
            GD.Print("This is the same as overriding _Ready()...");
            break;
        case NotificationProcess:
            var delta = GetProcessDeltaTime();
            GD.Print("This is the same as overriding _Process()...");
            break;
    }
}

Class Reference에서 각 클래스의 문서에서는 받을 수 있는 알림을 보여줍니다. 하지만 대부분의 경우, GDScript에서 다시 정의하는 것을 더 간단하게 할 수 있는 함수를 제공합니다.

다시 정의할 수 있는 함수

아래에서 설명하는 다시 정의할 수 있는 함수는 노드에 적용할 수 있습니다:

func _enter_tree():
    # When the node enters the Scene Tree, it becomes active
    # and  this function is called. Children nodes have not entered
    # the active scene yet. In general, it's better to use _ready()
    # for most cases.
    pass

func _ready():
    # This function is called after _enter_tree, but it ensures
    # that all children nodes have also entered the Scene Tree,
    # and became active.
    pass

func _exit_tree():
    # When the node exits the Scene Tree, this function is called.
    # Children nodes have all exited the Scene Tree at this point
    # and all became inactive.
    pass

func _process(delta):
    # This function is called every frame.
    pass

func _physics_process(delta):
    # This is called every physics frame.
    pass
public override void _EnterTree()
{
    // When the node enters the Scene Tree, it becomes active
    // and  this function is called. Children nodes have not entered
    // the active scene yet. In general, it's better to use _ready()
    // for most cases.
    base._EnterTree();
}

public override void _Ready()
{
    // This function is called after _enter_tree, but it ensures
    // that all children nodes have also entered the Scene Tree,
    // and became active.
    base._Ready();
}

public override void _ExitTree()
{
    // When the node exits the Scene Tree, this function is called.
    // Children nodes have all exited the Scene Tree at this point
    // and all became inactive.
    base._ExitTree();
}

public override void _Process(float delta)
{
    // This function is called every frame.
    base._Process(delta);
}

public override void _PhysicsProcess(float delta)
{
    // This is called every physics frame.
    base._PhysicsProcess(delta);
}

앞서 언급했듯이, 이러한 함수는 알림 시스템 대신 사용하는 것이 좋습니다.

노드 만들기

다른 클래스 기반 데이터 유형과 마찬가지로, 코드로 노드를 만들려면 .new() 메서드를 호출해야 합니다. 예를 들어:

var s
func _ready():
    s = Sprite.new() # Create a new sprite!
    add_child(s) # Add it as a child of this node.
private Sprite _sprite;

public override void _Ready()
{
    base._Ready();

    _sprite = new Sprite(); // Create a new sprite!
    AddChild(_sprite); // Add it as a child of this node.
}

씬 내부 또는 외부에 있는 노드를 삭제하려면 free()를 사용해야 합니다:

func _someaction():
    s.free() # Immediately removes the node from the scene and frees it.
public void _SomeAction()
{
    _sprite.Free(); // Immediately removes the node from the scene and frees it.
}

노드가 삭제되면 모든 하위 노드도 삭제됩니다. 따라서 수동으로 노드를 삭제하는 것이 수동으로 나타내는 것보다 훨씬 간단합니다. 기본 노드를 삭제하면 하위 트리에 있는 다른 모든 것이 삭제됩니다.

현재 "차단된" 노드를 삭제하려는 상황이 발생할 수 있습니다. 차단된 노드는 시그널을 방출하고 있거나 함수를 호출하고 있죠. 이것은 게임을 망칠 것입니다. 디버거(Debugger)로 Godot를 실행하면 이 경우를 발견하고 경고를 표시하는 경우가 많습니다.

노드를 삭제하는 가장 안전한 방법은 Node.queue_free()를 사용하는 것입니다. 이것은 대기 상태일 때 노드를 안전하게 삭제합니다.

func _someaction():
    s.queue_free() # Removes the node from the scene and frees it when it becomes safe to do so.
public void _SomeAction()
{
    _sprite.QueueFree(); // Removes the node from the scene and frees it when it becomes safe to do so.
}

씬 인스턴스하기

코드에서 씬을 인스턴스하는 작업은 두 단계로 이루어집니다. 첫 번째 방법은 하드 드라이브에서 씬을 불러오는 것입니다:

var scene = load("res://myscene.tscn") # Will load when the script is instanced.
var scene = GD.Load<PackedScene>("res://myscene.tscn"); // Will load when the script is instanced.

(GDScript인 경우라면) 구문 분석 시간 동안 프리로드(Preload)를 할 수 있어서 더 편리합니다:

var scene = preload("res://myscene.tscn") # Will load when parsing the script.

그러나 씬(Scene)은 아직 노드가 아닙니다. 이것은 PackedScene(포장된 씬)이라는 특수 리소스에 들어 있습니다. 실제 노드를 만들려면 PackedScene.instance() 함수를 호출해야 합니다. 그러면 PackedScene은 활성 씬에 추가할 수 있는 노드 트리를 반환합니다:

var node = scene.instance()
add_child(node)
var node = scene.Instance();
AddChild(node);

이 두 단계 처리의 이점은 PackedScene이 불러온 상태로 유지되고 언제나 사용될 준비를 합니다. 이렇게 해서 인스턴스를 원하는 만큼 만들 수 있습니다. 이것은 특히 활성 씬에서 많은 적, 총알, 그 외 다른 개체를 신속하게 인스턴스하는 데 유용합니다.

스크립트를 클래스로 등록하기

Godot는 편집기의 각 스크립트를 등록하기 위한 "스크립트 클래스(Script Class)" 기능을 갖고 있습니다. 기본적으로 이름이 없는 스크립트에 접근하려면 파일을 직접 불러와야 합니다.

스크립트에 이름을 짓고 클래스의 이름을 뜻하는 class_name 키워드로 스크립트를 유형으로 등록할 수 있습니다. 쉼표와 이미지로 향하는 경로를 선택적으로 추가하면 아이콘으로도 사용할 수 있습니다. 그런 다음 노드(Node)나 리소스(Resource) 만들기 대화 상자에서 당신의 새 유형을 찾을 수 있습니다.

extends Node

# Declare the class name here
class_name ScriptName, "res://path/to/optional/icon.svg"

func _ready():
    var this = ScriptName           # reference to the script
    var cppNode = MyCppNode.new()   # new instance of a class named MyCppNode

    cppNode.queue_free()
../../_images/script_class_nativescript_example.png

경고

Godot 3.1에서는:

  • 오직 GDScript와 NativeScript만 가능합니다. 즉, C++와 다른 GDNative의 영향을 받는 언어만 스크립트를 등록할 수 있습니다.
  • 오직 GDScript만 각 이름이 있는 스크립트에 전역 변수를 만듭니다.