Lingara Lingara Dokumentation Ratgeber API Bibliotheken Apps Erstellen Einbetten Web-App
Sprache: Deutsch

Godot

Diese Seite ist aus dem Englischen übersetzt. Wenn die beiden voneinander abweichen, ist die englische Seite maßgeblich. Englische Seite lesen

Godot bietet zwei Sprachen, und Lingara hat für jede ein Kit. Ein GDScript-Spiel verwendet das Lingara-Addon. Ein Godot-C#-Spiel verwendet Lingara.Embed, dasselbe Paket, das ein Unity-Spiel verwendet. Verwenden Sie in einem Projekt das eine oder das andere, nie beide. Die Tabs unten zeigen jedes.

Installation

GDScript. Installieren Sie das Lingara-Addon aus der Godot Asset Library und aktivieren Sie es dann unter Project Settings, Plugins. Beim Aktivieren wird ein Autoload namens Lingara hinzugefügt, den jede Szene unter /root/Lingara erreicht.

C#. Fügen Sie Lingara.Embed Ihrem Projekt als Paketreferenz hinzu. Der Beispielknoten unten, LingaraEmbedNode, ist nicht Teil des Pakets: Fügen Sie seinen Quellcode zu Ihrem Projekt hinzu und setzen Sie ihn in eine Szene.

Tokens von Ihrem Server erhalten

Keines der beiden Kits hält ein Secret. Wenn der Kern ein Token braucht, fragt er Ihren eigenen Server, der mit einem Server-Kit eines ausstellt und mit dem Token und seinem Ablaufzeitpunkt antwortet.

Beim Addon legen Sie die Token-Route Ihres Servers unter Project Settings fest, und Lingara ruft sie in einem eigenen Thread auf. Braucht die Route die Sitzung des Spielers, setzen Sie sie stattdessen zur Laufzeit, denn Projekteinstellungen werden mit Ihrem Export ausgeliefert. Oder Sie verbinden token_needed und beantworten jede Anfrage selbst. Bei C# sendet der Beispielknoten immer dann einen POST an Ihre Token-Route, wenn der Client ein Token anfordert. In beiden Fällen melden Sie einen Fehlschlag mit token_unavailable oder TokenUnavailable, damit die wartenden Aufrufe sofort fehlschlagen.

Events in jedem Frame verarbeiten

Der Kern führt seine Netzwerk-I/O in einem eigenen Thread aus. Der Autoload des Addons leert sie einmal pro Frame und sendet pro Event ein Signal, sodass Sie die Signale verbinden, die Ihr Spiel braucht, und selbst nie etwas leeren. Der C#-Knoten leert in _process, höchstens 256 Events pro Frame, und sendet jedes erneut als Godot-Signal.

Dieselben Signale tragen gestreamte NPC-Antworten (siehe NPC-Dialoge) und Übungsaktualisierungen (siehe Übungsergebnisse).

<PackageReference Include="Lingara.Embed" Version="0.1.0-alpha.6" />
Sprache des Codebeispiels

Godot C#

// Your own server mints the token: never put a client secret in a game. The
// continuation may run off the main thread, which the client allows.
private async Task FetchToken()
{
    try
    {
        using var response = await Http.PostAsync(TokenUrl, new StringContent("{}"));
        response.EnsureSuccessStatusCode();
        using var body = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
        Client?.ProvideToken(body.RootElement.GetProperty("token").GetString(), body.RootElement.GetProperty("expires_at").GetString());
    }
    catch (Exception e) when (e is HttpRequestException || e is JsonException || e is KeyNotFoundException)
    {
        Client?.TokenUnavailable(e.Message); // fail the waiting calls now
    }
}

GDScript

# The common case: set Project Settings > lingara/token_url to your server's
# token route, which answers exactly {"token", "expires_at"}. Lingara POSTs to
# it on its own thread whenever it needs a token. If the route needs the
# player's session, set it at run time instead (never in ProjectSettings,
# which ships inside the export):
func use_my_session(session_cookie: String) -> void:
	lingara.set_token_endpoint("https://game.example.com/lingara-token", {"cookie": session_cookie})


# Or answer each token_needed yourself, with whatever login your game has.
func _on_token_needed(_request: int, _reason: String) -> void:
	var http := HTTPRequest.new()
	add_child(http)
	http.request("https://game.example.com/lingara-token", [], HTTPClient.METHOD_POST, "{}")
	var reply: Array = await http.request_completed
	http.queue_free()
	var body: Variant = JSON.parse_string((reply[3] as PackedByteArray).get_string_from_utf8())
	if reply[1] == 200 and body is Dictionary:
		lingara.provide_token(body["token"], body["expires_at"])
	else:
		lingara.token_unavailable("the token route answered %d" % reply[1])
Sprache des Codebeispiels

Godot C#

public override void _Process(double delta)
{
    if (Client == null) return;
    events.Clear();
    Client.Drain(events); // at most 256 a frame; the rest wait for the next
    foreach (var e in events)
    {
        switch (e)
        {
            case EmbedEvent.TokenNeeded _: _ = FetchToken(); break;
            case EmbedEvent.NpcTurn { Update: NpcTurnUpdate.Fragment f } t:
                EmitSignal(SignalName.NpcTurnFragment, t.Dialogue.Value, t.Request.Value, f.Text); break;
            case EmbedEvent.NpcTurn { Update: NpcTurnUpdate.Finished f } t:
                EmitSignal(SignalName.NpcTurnFinished, t.Dialogue.Value, t.Request.Value, f.Text); break;
            case EmbedEvent.Practice p: EmitSignal(SignalName.PracticeChanged, p.Session.Value, p.Snapshot.Phase.ToString()); break;
            case EmbedEvent.Failed f: EmitSignal(SignalName.Failed, f.Error.KindName, f.Error.Message, f.Error.Request?.Value ?? 0); break;
            case EmbedEvent.Log log: GD.Print($"[lingara] {log.Level} {log.Target}: {log.Message}"); break;
        }
    }
}

GDScript

# The Lingara autoload the plugin adds, reached by path and typed, so every
# call and signal below is checked against LingaraEmbed.
@onready var lingara: LingaraEmbed = get_node("/root/Lingara")

# Enable the plugin (Project > Project Settings > Plugins): it adds the
# Lingara autoload, which drains the core once per frame and emits one signal
# per event. Connect the signals your game needs; you never call drain.
func _ready() -> void:
	lingara.token_needed.connect(_on_token_needed)
	lingara.event_received.connect(_on_event)
	lingara.error_raised.connect(func(error: Dictionary) -> void: push_warning(error.message))
	lingara.practice_changed.connect(_on_practice_changed)
	lingara.practice_finished.connect(_on_practice_finished)
	mei.replied.connect(_on_mei_replied)


func _on_event(_id: String, event_type: String, _created_at: String, data: Dictionary) -> void:
	if event_type == "lesson_plan.ready":
		print("a plan is ready: ", data.get("plan_id"))