Lingara Lingara Documentación Guías API Bibliotecas Apps Crear Integración Aplicación web
Idioma: Español

Godot

Esta página está traducida del inglés. Si ambas difieren, la página en inglés es la correcta. Leer la página en inglés

Godot ofrece dos lenguajes, y Lingara tiene un kit para cada uno. Un juego en GDScript usa el addon de Lingara. Un juego de Godot en C# usa Lingara.Embed, el mismo paquete que usa un juego de Unity. Usa uno u otro en un proyecto, nunca ambos. Las pestañas de abajo muestran cada uno.

Instalación

GDScript. Instala el addon de Lingara desde la Godot Asset Library y actívalo en Project Settings, Plugins. Al activarlo se añade un autoload llamado Lingara, al que cada escena accede en /root/Lingara.

C#. Añade Lingara.Embed a tu proyecto como referencia de paquete. El nodo de ejemplo de abajo, LingaraEmbedNode, no forma parte del paquete: añade su código fuente a tu proyecto y colócalo en una escena.

Obtener tokens de tu servidor

Ninguno de los dos kits guarda un secreto. Cuando el núcleo necesita un token, se lo pide a tu propio servidor, que emite uno con un kit de servidor y responde con el token y su caducidad.

Con el addon, define la ruta de tokens de tu servidor en Project Settings y Lingara la llama en su propio hilo. Si la ruta necesita la sesión del jugador, defínela en tiempo de ejecución, porque la configuración del proyecto se distribuye dentro de tu exportación. O conecta token_needed y responde tú mismo a cada solicitud. Con C#, el nodo de ejemplo hace una petición a tu ruta de tokens cada vez que el cliente pide un token. En ambos casos, informa de un fallo con token_unavailable o TokenUnavailable, para que las llamadas en espera fallen de inmediato.

Procesar los eventos en cada fotograma

El núcleo ejecuta su E/S de red en su propio hilo. El autoload del addon lo vacía una vez por fotograma y emite una señal por evento, así que conectas las señales que necesita tu juego y nunca vacías nada tú mismo. El nodo de C# vacía en _process, como máximo 256 eventos por fotograma, y vuelve a emitir cada uno como una señal de Godot.

Las mismas señales transportan las respuestas de PNJ en streaming (consulta Diálogo con PNJ) y las actualizaciones de práctica (consulta resultados de práctica).

<PackageReference Include="Lingara.Embed" Version="0.1.0-alpha.6" />
Lenguaje del ejemplo de código

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])
Lenguaje del ejemplo de código

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