Lingara Lingara Documentation Guides API Bibliothèques Applications Créer Intégration Application web
Langue: Français

Godot

Cette page est traduite de l'anglais. En cas de différence, la page anglaise fait foi. Lire la page en anglais

Godot propose deux langages, et Lingara a un kit pour chacun. Un jeu en GDScript utilise l’addon Lingara. Un jeu Godot en C# utilise Lingara.Embed, le même paquet qu’utilise un jeu Unity. Utilisez l’un ou l’autre dans un projet, jamais les deux. Les onglets ci-dessous montrent chacun d’eux.

Installation

GDScript. Installez l’addon Lingara depuis la Godot Asset Library, puis activez-le dans Project Settings, Plugins. Son activation ajoute un autoload nommé Lingara, que chaque scène atteint à /root/Lingara.

C#. Ajoutez Lingara.Embed à votre projet comme référence de paquet. Le nœud d’exemple ci-dessous, LingaraEmbedNode, ne fait pas partie du paquet : ajoutez son code source à votre projet et placez-le dans une scène.

Obtenir des jetons depuis votre serveur

Aucun des deux kits ne détient de secret. Lorsque le cœur a besoin d’un jeton, il le demande à votre propre serveur, qui en émet un avec un kit serveur et répond avec le jeton et son expiration.

Avec l’addon, définissez la route de jetons de votre serveur dans Project Settings et Lingara l’appelle sur son propre thread. Si la route a besoin de la session du joueur, définissez-la plutôt à l’exécution, car les paramètres du projet sont livrés dans votre export. Ou bien connectez token_needed et répondez vous-même à chaque demande. En C#, le nœud d’exemple envoie une requête à votre route de jetons chaque fois que le client demande un jeton. Dans les deux cas, signalez un échec avec token_unavailable ou TokenUnavailable, afin que les appels en attente échouent immédiatement.

Traiter les événements à chaque frame

Le cœur exécute ses E/S réseau sur son propre thread. L’autoload de l’addon le vide une fois par frame et émet un signal par événement : vous connectez donc les signaux dont votre jeu a besoin et ne videz jamais rien vous-même. Le nœud C# vide la file dans _process, au plus 256 événements par frame, et réémet chacun sous forme de signal Godot.

Les mêmes signaux transportent les réponses de PNJ en flux (voir Dialogue de PNJ) et les mises à jour de pratique (voir résultats de pratique).

<PackageReference Include="Lingara.Embed" Version="0.1.0-alpha.6" />
Langage de l'exemple de code

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])
Langage de l'exemple de code

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