Pisanie Skryptów (kontynuacja)

Przetwarzanie

Wiele akcji w Godot jest uruchamianych poprzez wywołania zwrotne i funkcje wirtualne, dlatego nie ma też potrzeby pisać kodu, który działa bez przerwy.

Jednakże często zdarza się, że skrypt musi być przetworzony co każdą klatkę. Istnieją dwa rodzaje przetwarzania: bezczynne (idle processing) oraz procesów fizycznych (physics processing).

Bezczynne przetwarzanie jest aktywowane, gdy w skrypcie znajduje się metoda Node._process(). Może być ono włączane i wyłączane za pomocą funkcji Node.set_process().

Ta metoda wykona się za każdym razem, kiedy rysowana jest klatka:

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

Trzeba pamiętać, że częstotliwość z jaką będzie się wykonywać metoda _process() zależy od tego w ilu klatkach na sekundę (FPS) działa Twoja aplikacja. Ta wartość może się zmieniać w zależności od czasu i urządzenia.

Pomocny w tym przypadku będzie parametr delta, który zawiera czas w sekundach jako liczba rzeczywista (float), jaki upłynął od poprzedniego wywołania _process().

Ten parametr jest stosowany by być zawsze pewnym, że czynność zawsze będzie trwała określony czas, bez względu na ilość FPS.

Na przykład, ruch jest często mnożony przez czas od ostatniej klatki (delta), aby prędkość ruchu była stała i niezależna od częstotliwości wyświetlanych klatek.

Przetwarzanie operacji fizycznych w _physics_process() jest podobne, ale powinno być używane do procesów, które muszą wystąpić przed każdym obliczeniem fizyki, takich jak kontrolowanie postaci. Zawsze działa przed obliczaniem fizyki i jest wywoływany w ustalonych odstępach czasu: domyślnie 60 razy na sekundę. Można zmienić odstęp czasu pomiędzy wywoływaniem funkcji _physics_process() w ustawieniach projektu, przechodząc do zakładki Physics -> Common -> Physics Fps.

Natomiast funkcja _process() nie jest zsynchronizowana z fizyką. Częstotliwość wywoływania nie jest stała, a zależy od ilości FPS-ów, które z kolei zależą od sprzętu i optymalizacji samej gry. Jej wykonanie następuje po obliczeniu fizyki w grach jednowątkowych.

Prostym sposobem na sprawdzenie działania funkcji _process() w praktyce, jest utworzenie sceny z pojedynczym węzłem Label, z następującym skryptem:

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.
    }
}

Który będzie pokazywał licznik zwiększający każdą klatkę.

Grupy

Grupy w Godocie działają jak tagi, które można spotkać w innych aplikacjach. Dany Węzeł może być dodany do dowolnej liczby grup. Jest to użyteczne narzędzie do organizowania rozbudowanych scen. Można je ustawić na dwa sposoby. Pierwszy poprzez użycie interfejsu użytkownika klikając na przycisk Grupy pod panelem Węzeł:

../../_images/groups_in_nodes.png

A drugą z poziomu kodu. Skrypt poniżej dodałby aktualny węzeł do grupy enemies, gdy tylko pojawi się w drzewie sceny.

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

    AddToGroup("enemies");
}

W ten sposób, jeśli gracz zostanie wykryty podczas skradania się do tajnej bazy, wszyscy wrogowie zostaną powiadomieni za pomocą 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");
}

Powyższy kod wywołuje funkcję ``wykryto_gracza``(player_was_discovered) w kodzie każdego członka grupy ``przeciwnicy``(enemies).

Możliwe jest również uzyskanie pełnej listy węzłów z grupy wrogowie przez wywołanie SceneTree.get_nodes_in_group():

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

Klasa SceneTree dostarcza wielu przydatnych metod, takich jak interakcja ze scenami, ich hierarchią i grupami węzłów. Pozwala w łatwy sposób przełączać sceny, przeładowywać je, kończyć grę lub spauzować i odpauzować ją. Sprawdź to, jeśli masz trochę czasu!

Powiadomienia

Godot posiada system powiadomień. Zazwyczaj nie są one potrzebne do skryptów, ponieważ są za bardzo niskopoziomowe i dla większości z nich dostępne są funkcje wirtualne - po prostu dobrze jest wiedzieć, że istnieją. Można na przykład dodać w Twoim skrypcie funkcję 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;
    }
}

Dokumentacja każdej klasy w Class Reference pokazuje powiadomienia, które może otrzymać. Jednak w większości przypadków GDScript zapewnia prostsze, możliwe do nadpisania funkcje.

Funkcje możliwe do nadpisania

Takie możliwe do nadpisania funkcje, opisane poniżej, mogą być stosowane do węzłów:

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);
}

Jak wspomniano wcześniej, lepiej jest używać tych funkcji zamiast systemu powiadomień.

Tworzenie węzłów

Aby utworzyć węzeł w kodzie, należy wywołać metodę .new(), tak jak w przypadku każdego innego zbioru danych opartego na klasie. Na przykład:

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.
}

Aby usunąć węzeł, zarówno wewnątrz, jak i na zewnątrz sceny, należy użyć 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.
}

Gdy węzeł zostanie uwolniony, uwalnia również wszystkie węzły pochodne. Z tego powodu ręczne usuwanie węzłów jest znacznie prostsze niż się wydaje. Zwolnij węzeł bazowy i wszystko inne w drzewie podrzędnym zniknie.

Może się zdarzyć, że chcemy usunąć węzeł, który jest obecnie "zablokowany", ponieważ emituje sygnał lub wywołuje funkcję. To wywoła błąd gry. Uruchomienie Godota z debuggerem często wyłapuje tą sytuację i ostrzega przed nią.

Najbezpieczniejszym sposobem na usunięcie węzła jest użycie Node.queue_free(). Węzeł zostanie bezpiecznie usunięty, podczas bezczynności.

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.
}

Instancjowanie scen

Instancjowanie sceny z kodu odbywa się w dwóch krokach. Pierwszym z nich jest wczytanie sceny z dysku twardego:

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.

Wstępne ładowanie (preloading) może być wygodniejsze, gdyż ma miejsce podczas parsowania (tylko GDScript):

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

Ale scene nie jest jeszcze węzłem. Jest zapakowany w specjalny zasób o nazwie PackedScene. Aby utworzyć węzeł należy wywołać funkcję PackedScene.instance(). Spowoduje to zwrócenie drzewa węzłów, które można dodać do aktywnej sceny:

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

Zaletą tego dwuetapowego procesu jest to, że zapakowana scena może być załadowana i gotowa do użycia, dzięki czemu można utworzyć dowolną liczbę instancji. Jest to szczególnie przydatne do szybkiego wywołania kilku wrogów, pocisków i innych podmiotów na aktywnej scenie.

Rejstrowanie skryptów jako klasy

Godot pozwala rejestrować konkretne skrypty jako klasy bezpośrednio w Edytorze. Domyślnie nienazwany skrypt może zostać otwarty tylko poprzez bezpośrednie załadowanie pliku ze skryptem.

Możesz nazwać skrypt i zarejestrować go jako typ w edytorze używając słowa kluczowego class_name wraz z nazwą klasy. Opcjonalnie po przecinku można dodać ścieżkę do zdjęcia jako ikonę. Tak utworzony nowy typ można znaleźć w oknie kreatywnym Węzła lub "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

Ostrzeżenie

W Godot 3.1:

  • Tylko GDScript i NativeScript, tj. C++ i inne języki wspierane przez GDNative, mogą rejestrować skrypty.
  • Tylko GDScript tworzy globalne zmienne dla każdego nazwanego skryptu.