Lingara Lingara Documentatie Gidsen API Bibliotheken Apps Bouwen Insluiten Webapp
Taal: Nederlands

Godot

Deze pagina is vertaald uit het Engels. Als de twee verschillen, is de Engelse pagina juist. Lees de Engelse pagina

Godot biedt twee talen, en Lingara heeft voor elk een kit. Een GDScript-game gebruikt de Lingara-addon. Een Godot C#-game gebruikt Lingara.Embed, hetzelfde pakket dat een Unity-game gebruikt. Gebruik in een project het een of het ander, nooit allebei. De tabbladen hieronder laten elk zien.

Installeren

GDScript. Installeer de Lingara-addon uit de Godot Asset Library en schakel hem daarna in onder Project Settings, Plugins. Inschakelen voegt een autoload toe met de naam Lingara, die elke scène bereikt op /root/Lingara.

C#. Voeg Lingara.Embed toe aan je project als pakketverwijzing. De voorbeeldnode hieronder, LingaraEmbedNode, maakt geen deel uit van het pakket: voeg de broncode ervan toe aan je project en zet hem in een scène.

Tokens van je server krijgen

Geen van beide kits bewaart een secret. Als de kern een token nodig heeft, vraagt hij het aan je eigen server, die er een uitgeeft met een serverkit en antwoordt met het token en de vervaltijd.

Met de addon stel je de tokenroute van je server in onder Project Settings, en Lingara roept die aan op een eigen thread. Als de route de sessie van de speler nodig heeft, stel je hem in plaats daarvan tijdens runtime in, omdat projectinstellingen in je export worden meegeleverd. Of je verbindt token_needed en beantwoordt elk verzoek zelf. Met C# doet de voorbeeldnode een POST naar je tokenroute wanneer de client om een token vraagt. Meld in beide gevallen een mislukking met token_unavailable of TokenUnavailable, zodat de wachtende aanroepen meteen mislukken.

Events elk frame verwerken

De kern voert zijn netwerk-I/O uit op een eigen thread. De autoload van de addon leegt die één keer per frame en zendt per event één signaal uit, zodat je de signalen verbindt die je game nodig heeft en zelf nooit iets leegt. De C#-node leegt in _process, maximaal 256 events per frame, en zendt elk opnieuw uit als Godot-signaal.

Dezelfde signalen dragen gestreamde NPC-antwoorden (zie NPC-dialoog) en oefenupdates (zie oefenresultaten).

<PackageReference Include="Lingara.Embed" Version="0.1.0-alpha.6" />
Taal van het codevoorbeeld

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])
Taal van het codevoorbeeld

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"))