Pisanie Skryptów (kontynuacja)

Przetwarzanie

Wiele akcji w Godot jest uruchamianych poprzez wywołania zwrotne i funkcje wirtualne, dla tego 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...
}

It’s important to bear in mind that the frequency with which _process() will be called depends on how many frames per second (FPS) your application is running at. This rate can vary over time and devices.

Parametr delta to liczba zmiennoprzecinkowa, zawierająca czas w sekundach, 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, aby prędkość ruchu była stała i niezależna od częstotliwości odświeżania.

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() ustawieniami projektu, przechodząc do zakładki Physics -> Common -> Physics Fps.

Funkcja _process() nie jest jednak zsynchronizowana z fizyką. Częstotliwość odświeżania nie jest stała i zależy od optymalizacji sprzętu i gier. Jej wykonanie następuje po obliczeniu fizyki w grach jednowątkowych.

A simple way to see the _process() function at work is to create a scene with a single Label node, with the following script:

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

Groups in Godot work like tags you might have come across in other software. A node can be added to as many groups as desired. This is a useful feature for organizing large scenes. There are two ways to add nodes to groups. The first is from the UI, using the Groups button under the Node panel:

../../_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 has a system of notifications. These are usually not needed for scripting, as it’s too low-level and virtual functions are provided for most of them. It’s just good to know they exist. For example, you may add an Object._notification() function in your script:

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

When a node is freed, it also frees all its child nodes. Because of this, manually deleting nodes is much simpler than it appears. Free the base node and everything else in the subtree goes away with it.

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ę ostrzega przed nią.

The safest way to delete a node is by using Node.queue_free(). This erases the node safely during idle.

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 has a „Script Class” feature to register individual scripts with the Editor. By default, you can only access unnamed scripts by loading the file directly.

You can name a script and register it as a type in the editor with the class_name keyword followed by the class’s name. You may add a comma and an optional path to an image to use as an icon. You will then find your new type in the Node or Resource creation dialog.

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.